Merge branch 'master' into notebook/outputs

This commit is contained in:
Johannes Rieken 2021-02-11 15:03:41 +01:00
commit 6c92a6605c
104 changed files with 3825 additions and 998 deletions

View file

@ -13,5 +13,6 @@
**/extensions/**/out/**
**/extensions/**/build/**
**/extensions/markdown-language-features/media/**
**/extensions/markdown-language-features/notebook-out/**
**/extensions/typescript-basics/test/colorize-fixtures/**
**/extensions/**/dist/**

View file

@ -86,6 +86,8 @@ module.exports.indentationFilter = [
'!**/*.Dockerfile',
'!**/*.dockerfile',
'!extensions/markdown-language-features/media/*.js',
'!extensions/markdown-language-features/notebook-out/*.js',
'!extensions/markdown-notebook-math/notebook-out/*.js',
'!extensions/simple-browser/media/*.js',
];

View file

@ -29,6 +29,7 @@ exports.dirs = [
'extensions/json-language-features',
'extensions/json-language-features/server',
'extensions/markdown-language-features',
'extensions/markdown-notebook-math',
'extensions/merge-conflict',
'extensions/microsoft-authentication',
'extensions/npm',

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as MarkdownIt from 'markdown-it';
declare const acquireNotebookRendererApi: any;
type extendMarkdownItFnType = (
(f: (md: MarkdownIt.MarkdownIt) => void) => void
);
(function () {
const markdownIt = new MarkdownIt();
(globalThis as any).extendMarkdownIt = ((f: (md: MarkdownIt.MarkdownIt) => void) => {
f(markdownIt);
}) as extendMarkdownItFnType;
const notebook = acquireNotebookRendererApi('notebookCoreTestRenderer');
notebook.onDidCreateMarkdown(({ element, content }: any) => {
const rendered = markdownIt.render(content);
element.innerHTML = rendered;
});
}());

View file

@ -30,6 +30,13 @@
"onCustomEditor:vscode.markdown.preview.editor"
],
"contributes": {
"notebookMarkdownRenderer": [
{
"id": "markdownItRenderer",
"displayName": "Markdown it renderer",
"entrypoint": "./notebook-out/index.js"
}
],
"commands": [
{
"command": "markdown.showPreview",

View file

@ -0,0 +1,27 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const path = require('path');
module.exports = {
entry: {
index: './notebook/index.ts'
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: ['.tsx', '.ts', '.js']
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'notebook-out')
}
};

View file

@ -0,0 +1,12 @@
test/**
test-workspace/**
src/**
tsconfig.json
out/test/**
out/**
extension.webpack.config.js
extension-browser.webpack.config.js
cgmanifest.json
yarn.lock
preview-src/**
webpack.config.js

View file

@ -0,0 +1,3 @@
# Markdown Notebook Math support
**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled.

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* 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 markdownIt from 'markdown-it';
import 'katex/dist/katex.min.css';
declare const extendMarkdownIt: undefined | (
(f: (md: markdownIt.MarkdownIt) => void) => void
);
(function () {
const katex = require('@iktakahiro/markdown-it-katex');
if (typeof extendMarkdownIt !== 'undefined') {
extendMarkdownIt((md: markdownIt.MarkdownIt) => {
md.use(katex);
});
}
}());

View file

@ -0,0 +1,12 @@
{
"extends": "../../shared.tsconfig.json",
"compilerOptions": {
"outDir": "./dist/",
"jsx": "react",
"lib": [
"es2018",
"DOM",
"DOM.Iterable"
]
}
}

View file

@ -0,0 +1,45 @@
{
"name": "markdown-notebook-math",
"displayName": "%displayName%",
"description": "%description%",
"version": "1.0.0",
"icon": "icon.png",
"publisher": "vscode",
"enableProposedApi": true,
"license": "MIT",
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
"engines": {
"vscode": "^1.54.0"
},
"categories": [
"Other"
],
"contributes": {
"notebookMarkdownRenderer": [
{
"id": "markdownItRenderer-katex",
"displayName": "Markdown it renderer",
"entrypoint": "./notebook-out/extension.js"
}
]
},
"scripts": {
"compile": "npm run build-notebook",
"watch": "npm run build-notebook",
"build-notebook": "npx webpack-cli --config webpack.notebook.js --mode production"
},
"dependencies": {
"@iktakahiro/markdown-it-katex": "^4.0.1"
},
"devDependencies": {
"@types/markdown-it": "^0.0.0",
"css-loader": "^5.0.2",
"markdown-it": "^12.0.4",
"style-loader": "^2.0.0",
"url-loader": "^4.1.1"
},
"repository": {
"type": "git",
"url": "https://github.com/microsoft/vscode.git"
}
}

View file

@ -0,0 +1,4 @@
{
"displayName": "Markdown Notebook math",
"description": "Provides rich language support for Markdown."
}

View file

@ -0,0 +1,35 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const path = require('path');
module.exports = {
entry: {
extension: './notebook/extension.ts',
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
},
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
use: ['url-loader?limit=100000']
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js']
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'notebook-out')
}
};

View file

@ -0,0 +1,315 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@iktakahiro/markdown-it-katex@^4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@iktakahiro/markdown-it-katex/-/markdown-it-katex-4.0.1.tgz#65ff9d12afd4c0b7684dd247abe7ce42fc1edac3"
integrity sha512-kGFooO7fIOgY34PSG8ZNVsUlKhhNoqhzW2kq94TNGa8COzh73PO4KsEoPOsQVG1mEAe8tg7GqG0FoVao0aMHaw==
dependencies:
katex "^0.12.0"
"@types/json-schema@^7.0.6":
version "7.0.7"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad"
integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==
"@types/markdown-it@^0.0.0":
version "0.0.0"
resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.0.tgz#8f6acaa5e3245e275f684e95deb3e518d1c6ab16"
integrity sha1-j2rKpeMkXidfaE6V3rPlGNHGqxY=
ajv-keywords@^3.5.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
ajv@^6.12.5:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
big.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
camelcase@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
colorette@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b"
integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==
commander@^2.19.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
css-loader@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.0.2.tgz#24f758dae349bad0a440c50d7e2067742e0899cb"
integrity sha512-gbkBigdcHbmNvZ1Cg6aV6qh6k9N6XOr8YWzISLQGrwk2mgOH8LLrizhkxbDhQtaLtktyKHD4970S0xwz5btfTA==
dependencies:
camelcase "^6.2.0"
cssesc "^3.0.0"
icss-utils "^5.1.0"
loader-utils "^2.0.0"
postcss "^8.2.4"
postcss-modules-extract-imports "^3.0.0"
postcss-modules-local-by-default "^4.0.0"
postcss-modules-scope "^3.0.0"
postcss-modules-values "^4.0.0"
postcss-value-parser "^4.1.0"
schema-utils "^3.0.0"
semver "^7.3.4"
cssesc@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
emojis-list@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
entities@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
fast-deep-equal@^3.1.1:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
icss-utils@^5.0.0, icss-utils@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
indexes-of@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc=
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json5@^2.1.2:
version "2.2.0"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
dependencies:
minimist "^1.2.5"
katex@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/katex/-/katex-0.12.0.tgz#2fb1c665dbd2b043edcf8a1f5c555f46beaa0cb9"
integrity sha512-y+8btoc/CK70XqcHqjxiGWBOeIL8upbS0peTPXTvgrh21n1RiWWcIpSWM+4uXq+IAgNh9YYQWdc7LVDPDAEEAg==
dependencies:
commander "^2.19.0"
linkify-it@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.2.tgz#f55eeb8bc1d3ae754049e124ab3bb56d97797fb8"
integrity sha512-gDBO4aHNZS6coiZCKVhSNh43F9ioIL4JwRjLZPkoLIY4yZFwg264Y5lu2x6rb1Js42Gh6Yqm2f6L2AJcnkzinQ==
dependencies:
uc.micro "^1.0.1"
loader-utils@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0"
integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
json5 "^2.1.2"
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies:
yallist "^4.0.0"
markdown-it@^12.0.4:
version "12.0.4"
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.0.4.tgz#eec8247d296327eac3ba9746bdeec9cfcc751e33"
integrity sha512-34RwOXZT8kyuOJy25oJNJoulO8L0bTHYWXcdZBYZqFnjIy3NgjeoM3FmPXIOFQ26/lSHYMr8oc62B6adxXcb3Q==
dependencies:
argparse "^2.0.1"
entities "~2.1.0"
linkify-it "^3.0.1"
mdurl "^1.0.1"
uc.micro "^1.0.5"
mdurl@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
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.27:
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"
minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
nanoid@^3.1.20:
version "3.1.20"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788"
integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==
postcss-modules-extract-imports@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d"
integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==
postcss-modules-local-by-default@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c"
integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==
dependencies:
icss-utils "^5.0.0"
postcss-selector-parser "^6.0.2"
postcss-value-parser "^4.1.0"
postcss-modules-scope@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06"
integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==
dependencies:
postcss-selector-parser "^6.0.4"
postcss-modules-values@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c"
integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==
dependencies:
icss-utils "^5.0.0"
postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4:
version "6.0.4"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3"
integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==
dependencies:
cssesc "^3.0.0"
indexes-of "^1.0.1"
uniq "^1.0.1"
util-deprecate "^1.0.2"
postcss-value-parser@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
postcss@^8.2.4:
version "8.2.6"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.6.tgz#5d69a974543b45f87e464bc4c3e392a97d6be9fe"
integrity sha512-xpB8qYxgPuly166AGlpRjUdEYtmOWx2iCwGmrv4vqZL9YPVviDVPZPRXxnXr6xPZOdxQ9lp3ZBFCRgWJ7LE3Sg==
dependencies:
colorette "^1.2.1"
nanoid "^3.1.20"
source-map "^0.6.1"
punycode@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
schema-utils@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef"
integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==
dependencies:
"@types/json-schema" "^7.0.6"
ajv "^6.12.5"
ajv-keywords "^3.5.2"
semver@^7.3.4:
version "7.3.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97"
integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==
dependencies:
lru-cache "^6.0.0"
source-map@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
style-loader@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-2.0.0.tgz#9669602fd4690740eaaec137799a03addbbc393c"
integrity sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==
dependencies:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.6"
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
uniq@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=
uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
dependencies:
punycode "^2.1.0"
url-loader@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2"
integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==
dependencies:
loader-utils "^2.0.0"
mime-types "^2.1.27"
schema-utils "^3.0.0"
util-deprecate@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==

View file

@ -602,6 +602,18 @@
"description": "%configuration.suggest.completeJSDocs%",
"scope": "resource"
},
"javascript.suggest.jsdoc.generateReturns": {
"type": "boolean",
"default": true,
"markdownDescription": "%configuration.suggest.jsdoc.generateReturns%",
"scope": "resource"
},
"typescript.suggest.jsdoc.generateReturns": {
"type": "boolean",
"default": true,
"markdownDescription": "%configuration.suggest.jsdoc.generateReturns%",
"scope": "resource"
},
"typescript.locale": {
"type": [
"string",

View file

@ -68,6 +68,7 @@
"configuration.javascript.checkJs.experimentalDecorators.deprecation": "This setting has been deprecated in favor of `js/ts.implicitProjectConfig.experimentalDecorators`.",
"configuration.implicitProjectConfig.strictNullChecks": "Enable/disable [strict null checks](https://www.typescriptlang.org/tsconfig#strictNullChecks) in JavaScript and TypeScript files that are not part of a project. Existing `jsconfig.json` or `tsconfig.json` files override this setting.",
"configuration.implicitProjectConfig.strictFunctionTypes": "Enable/disable [strict function types](https://www.typescriptlang.org/tsconfig#strictFunctionTypes) in JavaScript and TypeScript files that are not part of a project. Existing `jsconfig.json` or `tsconfig.json` files override this setting.",
"configuration.suggest.jsdoc.generateReturns": "Enable/disable generating `@return` annotations for JSDoc templates. Requires using TypeScript 4.2+ in the workspace.",
"configuration.suggest.autoImports": "Enable/disable auto import suggestions.",
"taskDefinition.tsconfig.description": "The tsconfig file that defines the TS build.",
"javascript.suggestionActions.enabled": "Enable/disable suggestion diagnostics for JavaScript files in the editor.",

View file

@ -182,6 +182,7 @@ export default class FileConfigurationManager extends Disposable {
allowRenameOfImportPath: true,
includeAutomaticOptionalChainCompletions: config.get<boolean>('suggest.includeAutomaticOptionalChainCompletions', true),
provideRefactorNotApplicableReason: true,
generateReturnInDocTemplate: config.get<boolean>('suggest.jsdoc.generateReturns', true),
};
return preferences;

View file

@ -9,6 +9,7 @@ import { ITypeScriptServiceClient } from '../typescriptService';
import { conditionalRegistration, requireConfiguration } from '../utils/dependentRegistration';
import { DocumentSelector } from '../utils/documentSelector';
import * as typeConverters from '../utils/typeConverters';
import FileConfigurationManager from './fileConfigurationManager';
const localize = nls.loadMessageBundle();
@ -37,6 +38,7 @@ class JsDocCompletionProvider implements vscode.CompletionItemProvider {
constructor(
private readonly client: ITypeScriptServiceClient,
private readonly fileConfigurationManager: FileConfigurationManager,
) { }
public async provideCompletionItems(
@ -53,8 +55,12 @@ class JsDocCompletionProvider implements vscode.CompletionItemProvider {
return undefined;
}
const args = typeConverters.Position.toFileLocationRequestArgs(file, position);
const response = await this.client.execute('docCommentTemplate', args, token);
const response = await this.client.interruptGetErr(async () => {
await this.fileConfigurationManager.ensureConfigurationForDocument(document, token);
const args = typeConverters.Position.toFileLocationRequestArgs(file, position);
return this.client.execute('docCommentTemplate', args, token);
});
if (response.type !== 'response' || !response.body) {
return undefined;
}
@ -107,6 +113,9 @@ export function templateToSnippet(template: string): vscode.SnippetString {
out += post + ` \${${snippetIndex++}}`;
return out;
});
template = template.replace(/\* @returns[ \t]*$/gm, `* @returns \${${snippetIndex++}}`);
return new vscode.SnippetString(template);
}
@ -114,12 +123,14 @@ export function register(
selector: DocumentSelector,
modeId: string,
client: ITypeScriptServiceClient,
fileConfigurationManager: FileConfigurationManager,
): vscode.Disposable {
return conditionalRegistration([
requireConfiguration(modeId, 'suggest.completeJSDocs')
], () => {
return vscode.languages.registerCompletionItemProvider(selector.syntax,
new JsDocCompletionProvider(client),
new JsDocCompletionProvider(client, fileConfigurationManager),
'*');
});
}

View file

@ -72,7 +72,7 @@ export default class LanguageProvider extends Disposable {
import('./languageFeatures/formatting').then(provider => this._register(provider.register(selector, this.description.id, this.client, this.fileConfigurationManager))),
import('./languageFeatures/hover').then(provider => this._register(provider.register(selector, this.client))),
import('./languageFeatures/implementations').then(provider => this._register(provider.register(selector, this.client))),
import('./languageFeatures/jsDocCompletions').then(provider => this._register(provider.register(selector, this.description.id, this.client))),
import('./languageFeatures/jsDocCompletions').then(provider => this._register(provider.register(selector, this.description.id, this.client, this.fileConfigurationManager))),
import('./languageFeatures/organizeImports').then(provider => this._register(provider.register(selector, this.client, this.commandManager, this.fileConfigurationManager, this.telemetryReporter))),
import('./languageFeatures/quickFix').then(provider => this._register(provider.register(selector, this.client, this.fileConfigurationManager, this.commandManager, this.client.diagnosticsManager, this.telemetryReporter))),
import('./languageFeatures/refactor').then(provider => this._register(provider.register(selector, this.client, this.fileConfigurationManager, this.commandManager, this.telemetryReporter))),

View file

@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.54.0",
"distro": "f542aeaaf397d5c71e480442fe772dbe362726db",
"distro": "4e0b11c36e1bc4032ef3c28c82fdf336f7f48cf3",
"author": {
"name": "Microsoft Corporation"
},

View file

@ -163,8 +163,11 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem {
this.actionRunner.run(this._action, context);
}
// Only set the tabIndex on the element once it is about to get focused
// That way this element wont be a tab stop when it is not needed #106441
focus(): void {
if (this.element) {
this.element.tabIndex = 0;
this.element.focus();
this.element.classList.add('focused');
}
@ -173,10 +176,21 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem {
blur(): void {
if (this.element) {
this.element.blur();
this.element.tabIndex = -1;
this.element.classList.remove('focused');
}
}
setFocusable(): void {
if (this.element) {
this.element.tabIndex = 0;
}
}
get trapsArrowNavigation(): boolean {
return false;
}
protected updateEnabled(): void {
// implement in subclass
}
@ -259,14 +273,27 @@ export class ActionViewItem extends BaseActionViewItem {
this.updateChecked();
}
// Only set the tabIndex on the element once it is about to get focused
// That way this element wont be a tab stop when it is not needed #106441
focus(): void {
super.focus();
if (this.label) {
this.label.tabIndex = 0;
this.label.focus();
}
}
blur(): void {
if (this.label) {
this.label.tabIndex = -1;
}
}
setFocusable(): void {
if (this.label) {
this.label.tabIndex = 0;
}
}
updateLabel(): void {
if (this.options.label && this.label) {
this.label.textContent = this.getAction().label;
@ -320,7 +347,6 @@ export class ActionViewItem extends BaseActionViewItem {
if (this.label) {
this.label.removeAttribute('aria-disabled');
this.label.classList.remove('disabled');
this.label.tabIndex = 0;
}
if (this.element) {

View file

@ -140,6 +140,7 @@ export class ActionBar extends Disposable implements IActionRunner {
this._register(DOM.addDisposableListener(this.domNode, DOM.EventType.KEY_DOWN, e => {
const event = new StandardKeyboardEvent(e);
let eventHandled = true;
const focusedItem = typeof this.focusedItem === 'number' ? this.viewItems[this.focusedItem] : undefined;
if (previousKeys && (event.equals(previousKeys[0]) || event.equals(previousKeys[1]))) {
eventHandled = this.focusPrevious();
@ -147,6 +148,12 @@ export class ActionBar extends Disposable implements IActionRunner {
eventHandled = this.focusNext();
} else if (event.equals(KeyCode.Escape) && this.cancelHasListener) {
this._onDidCancel.fire();
} else if ((event.equals(KeyCode.Tab) || event.equals(KeyMod.Shift | KeyCode.Tab)) && focusedItem instanceof BaseActionViewItem && focusedItem.trapsArrowNavigation) {
if (event.equals(KeyCode.Tab)) {
this.focusNext();
} else {
this.focusPrevious();
}
} else if (this.isTriggerKeyEvent(event)) {
// Staying out of the else branch even if not triggered
if (this._triggerKeys.keyDown) {
@ -294,6 +301,11 @@ export class ActionBar extends Disposable implements IActionRunner {
item.setActionContext(this.context);
item.render(actionViewItemElement);
if (this.viewItems.every(i => !i.isEnabled()) && item instanceof BaseActionViewItem && item.isEnabled()) {
// We need to allow for the first enabled item to be focused on using tab navigation #106441
item.setFocusable();
}
if (index === null || index < 0 || index >= this.actionsList.children.length) {
this.actionsList.appendChild(actionViewItemElement);
this.viewItems.push(item);

View file

@ -78,7 +78,6 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
this.element.classList.add(...classNames);
this.element.tabIndex = 0;
this.element.setAttribute('role', 'button');
this.element.setAttribute('aria-haspopup', 'true');
this.element.setAttribute('aria-expanded', 'false');

View file

@ -54,6 +54,7 @@
/* TODO: actions should be part of the pane, but they aren't yet */
.monaco-pane-view .pane:hover > .pane-header.expanded > .actions,
.monaco-pane-view .pane:focus-within > .pane-header.expanded > .actions,
.monaco-pane-view .pane > .pane-header.actions-always-visible.expanded > .actions,
.monaco-pane-view .pane > .pane-header.focused.expanded > .actions {
display: initial;

View file

@ -10,8 +10,8 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { LinkedList } from 'vs/base/common/linkedList';
import { URI } from 'vs/base/common/uri';
export function isThenable<T>(obj: any): obj is Promise<T> {
return obj && typeof (<Promise<any>>obj).then === 'function';
export function isThenable<T>(obj: unknown): obj is Promise<T> {
return !!obj && typeof (obj as unknown as Promise<T>).then === 'function';
}
export interface CancelablePromise<T> extends Promise<T> {
@ -166,10 +166,10 @@ export class Throttler {
this.activePromise = promiseFactory();
return new Promise((resolve, reject) => {
this.activePromise!.then((result: any) => {
this.activePromise!.then((result: T) => {
this.activePromise = null;
resolve(result);
}, (err: any) => {
}, (err: unknown) => {
this.activePromise = null;
reject(err);
});
@ -179,7 +179,7 @@ export class Throttler {
export class Sequencer {
private current: Promise<any> = Promise.resolve(null);
private current: Promise<unknown> = Promise.resolve(null);
queue<T>(promiseTask: ITask<Promise<T>>): Promise<T> {
return this.current = this.current.then(() => promiseTask(), () => promiseTask());
@ -188,7 +188,7 @@ export class Sequencer {
export class SequencerByKey<TKey> {
private promiseMap = new Map<TKey, Promise<any>>();
private promiseMap = new Map<TKey, Promise<unknown>>();
queue<T>(key: TKey, promiseTask: ITask<Promise<T>>): Promise<T> {
const runningPromise = this.promiseMap.get(key) ?? Promise.resolve();
@ -321,7 +321,7 @@ export class ThrottledDelayer<T> {
}
trigger(promiseFactory: ITask<Promise<T>>, delay?: number): Promise<T> {
return this.delayer.trigger(() => this.throttler.queue(promiseFactory), delay) as any as Promise<T>;
return this.delayer.trigger(() => this.throttler.queue(promiseFactory), delay) as unknown as Promise<T>;
}
isTriggered(): boolean {
@ -488,7 +488,7 @@ export function firstParallel<T>(promiseList: Promise<T>[], shouldStop: (t: T) =
interface ILimitedTaskFactory<T> {
factory: ITask<Promise<T>>;
c: (value: T | Promise<T>) => void;
e: (error?: any) => void;
e: (error?: unknown) => void;
}
/**
@ -667,7 +667,7 @@ export class IntervalTimer implements IDisposable {
export class RunOnceScheduler {
protected runner: ((...args: any[]) => void) | null;
protected runner: ((...args: unknown[]) => void) | null;
private timeoutToken: any;
private timeout: number;
@ -827,7 +827,7 @@ export class IdleValue<T> {
private _didRun: boolean = false;
private _value?: T;
private _error: any;
private _error: unknown;
constructor(executor: () => T) {
this._executor = () => {
@ -1017,7 +1017,7 @@ export class IntervalCounter {
//#region
export type ValueCallback<T = any> = (value: T | Promise<T>) => void;
export type ValueCallback<T = unknown> = (value: T | Promise<T>) => void;
/**
* Creates a promise whose resolution or rejection can be controlled imperatively.
@ -1025,7 +1025,7 @@ export type ValueCallback<T = any> = (value: T | Promise<T>) => void;
export class DeferredPromise<T> {
private completeCallback!: ValueCallback<T>;
private errorCallback!: (err: any) => void;
private errorCallback!: (err: unknown) => void;
private rejected = false;
private resolved = false;
@ -1058,7 +1058,7 @@ export class DeferredPromise<T> {
});
}
public error(err: any) {
public error(err: unknown) {
return new Promise<void>(resolve => {
this.errorCallback(err);
this.rejected = true;
@ -1080,14 +1080,14 @@ export class DeferredPromise<T> {
//#region
export interface IWaitUntil {
waitUntil(thenable: Promise<any>): void;
waitUntil(thenable: Promise<unknown>): void;
}
export class AsyncEmitter<T extends IWaitUntil> extends Emitter<T> {
private _asyncDeliveryQueue?: LinkedList<[Listener<T>, Omit<T, 'waitUntil'>]>;
async fireAsync(data: Omit<T, 'waitUntil'>, token: CancellationToken, promiseJoin?: (p: Promise<any>, listener: Function) => Promise<any>): Promise<void> {
async fireAsync(data: Omit<T, 'waitUntil'>, token: CancellationToken, promiseJoin?: (p: Promise<unknown>, listener: Function) => Promise<unknown>): Promise<void> {
if (!this._listeners) {
return;
}
@ -1103,11 +1103,11 @@ export class AsyncEmitter<T extends IWaitUntil> extends Emitter<T> {
while (this._asyncDeliveryQueue.size > 0 && !token.isCancellationRequested) {
const [listener, data] = this._asyncDeliveryQueue.shift()!;
const thenables: Promise<any>[] = [];
const thenables: Promise<unknown>[] = [];
const event = <T>{
...data,
waitUntil: (p: Promise<any>): void => {
waitUntil: (p: Promise<unknown>): void => {
if (Object.isFrozen(thenables)) {
throw new Error('waitUntil can NOT be called asynchronous');
}
@ -1208,7 +1208,7 @@ export namespace Promises {
return undefined; // do not rethrow so that other promises can settle
})));
if (firstError) {
if (typeof firstError !== 'undefined') {
throw firstError;
}

View file

@ -28,7 +28,6 @@ export function toSlashes(osPath: string) {
* or `getRoot('\\server\shares\path') === \\server\shares\`
*/
export function getRoot(path: string, sep: string = posix.sep): string {
if (!path) {
return '';
}

View file

@ -15,15 +15,15 @@ export function isArray<T>(array: T | {}): array is T extends readonly any[] ? (
/**
* @returns whether the provided parameter is a JavaScript String or not.
*/
export function isString(str: any): str is string {
export function isString(str: unknown): str is string {
return (typeof str === 'string');
}
/**
* @returns whether the provided parameter is a JavaScript Array and each element in the array is a string.
*/
export function isStringArray(value: any): value is string[] {
return Array.isArray(value) && (<any[]>value).every(elem => isString(elem));
export function isStringArray(value: unknown): value is string[] {
return Array.isArray(value) && (<unknown[]>value).every(elem => isString(elem));
}
/**
@ -31,7 +31,7 @@ export function isStringArray(value: any): value is string[] {
* @returns whether the provided parameter is of type `object` but **not**
* `null`, an `array`, a `regexp`, nor a `date`.
*/
export function isObject(obj: any): obj is Object {
export function isObject(obj: unknown): obj is Object {
// The method can't do a type cast since there are type (like strings) which
// are subclasses of any put not positvely matched by the function. Hence type
// narrowing results in wrong results.
@ -46,21 +46,21 @@ export function isObject(obj: any): obj is Object {
* In **contrast** to just checking `typeof` this will return `false` for `NaN`.
* @returns whether the provided parameter is a JavaScript Number or not.
*/
export function isNumber(obj: any): obj is number {
export function isNumber(obj: unknown): obj is number {
return (typeof obj === 'number' && !isNaN(obj));
}
/**
* @returns whether the provided parameter is a JavaScript Boolean or not.
*/
export function isBoolean(obj: any): obj is boolean {
export function isBoolean(obj: unknown): obj is boolean {
return (obj === true || obj === false);
}
/**
* @returns whether the provided parameter is undefined.
*/
export function isUndefined(obj: any): obj is undefined {
export function isUndefined(obj: unknown): obj is undefined {
return (typeof obj === 'undefined');
}
@ -74,12 +74,12 @@ export function isDefined<T>(arg: T | null | undefined): arg is T {
/**
* @returns whether the provided parameter is undefined or null.
*/
export function isUndefinedOrNull(obj: any): obj is undefined | null {
export function isUndefinedOrNull(obj: unknown): obj is undefined | null {
return (isUndefined(obj) || obj === null);
}
export function assertType(condition: any, type?: string): asserts condition {
export function assertType(condition: unknown, type?: string): asserts condition {
if (!condition) {
throw new Error(type ? `Unexpected type, expected '${type}'` : 'Unexpected type');
}
@ -123,7 +123,7 @@ const hasOwnProperty = Object.prototype.hasOwnProperty;
/**
* @returns whether the provided parameter is an empty JavaScript Object or not.
*/
export function isEmptyObject(obj: any): obj is any {
export function isEmptyObject(obj: unknown): obj is object {
if (!isObject(obj)) {
return false;
}
@ -140,27 +140,27 @@ export function isEmptyObject(obj: any): obj is any {
/**
* @returns whether the provided parameter is a JavaScript Function or not.
*/
export function isFunction(obj: any): obj is Function {
export function isFunction(obj: unknown): obj is Function {
return (typeof obj === 'function');
}
/**
* @returns whether the provided parameters is are JavaScript Function or not.
*/
export function areFunctions(...objects: any[]): boolean {
export function areFunctions(...objects: unknown[]): boolean {
return objects.length > 0 && objects.every(isFunction);
}
export type TypeConstraint = string | Function;
export function validateConstraints(args: any[], constraints: Array<TypeConstraint | undefined>): void {
export function validateConstraints(args: unknown[], constraints: Array<TypeConstraint | undefined>): void {
const len = Math.min(args.length, constraints.length);
for (let i = 0; i < len; i++) {
validateConstraint(args[i], constraints[i]);
}
}
export function validateConstraint(arg: any, constraint: TypeConstraint | undefined): void {
export function validateConstraint(arg: unknown, constraint: TypeConstraint | undefined): void {
if (isString(constraint)) {
if (typeof arg !== constraint) {
@ -174,7 +174,7 @@ export function validateConstraint(arg: any, constraint: TypeConstraint | undefi
} catch {
// ignore
}
if (!isUndefinedOrNull(arg) && arg.constructor === constraint) {
if (!isUndefinedOrNull(arg) && (arg as any).constructor === constraint) {
return;
}
if (constraint.length === 1 && constraint.call(undefined, arg) === true) {
@ -204,8 +204,8 @@ export function getAllMethodNames(obj: object): string[] {
return methods;
}
export function createProxyObject<T extends object>(methodNames: string[], invoke: (method: string, args: any[]) => any): T {
const createProxyMethod = (method: string): () => any => {
export function createProxyObject<T extends object>(methodNames: string[], invoke: (method: string, args: unknown[]) => unknown): T {
const createProxyMethod = (method: string): () => unknown => {
return function () {
const args = Array.prototype.slice.call(arguments, 0);
return invoke(method, args);
@ -242,7 +242,7 @@ export type AddFirstParameterToFunctions<Target, TargetFunctionsReturnType, Firs
[K in keyof Target]:
// Function: add param to function
Target[K] extends (...args: any) => TargetFunctionsReturnType ? (firstArg: FirstParameter, ...args: Parameters<Target[K]>) => ReturnType<Target[K]> :
Target[K] extends (...args: any[]) => TargetFunctionsReturnType ? (firstArg: FirstParameter, ...args: Parameters<Target[K]>) => ReturnType<Target[K]> :
// Else: just leave as is
Target[K]

View file

@ -71,10 +71,8 @@ export interface IIPCOptions {
debugBrk?: number;
/**
* See https://github.com/microsoft/vscode/issues/27665
* Allows to pass in fresh execArgv to the forked process such that it doesn't inherit them from `process.execArgv`.
* e.g. Launching the extension host process with `--inspect-brk=xxx` and then forking a process from the extension host
* results in the forked process inheriting `--inspect-brk=xxx`.
* If set, starts the fork with empty execArgv. If not set, execArgv from the parent proces are inherited,
* except --inspect= and --inspect-brk= which are filtered as they would result in a port conflict.
*/
freshExecArgv?: boolean;
@ -198,6 +196,12 @@ export class Client implements IChannelClient, IDisposable {
forkOpts.execArgv = ['--nolazy', '--inspect-brk=' + this.options.debugBrk];
}
if (forkOpts.execArgv === undefined) {
// if not set, the forked process inherits the execArgv of the parent process
// --inspect and --inspect-brk can not be inherited as the port would conflict
forkOpts.execArgv = process.execArgv.filter(a => !/^--inspect(-brk)?=/.test(a)); // remove
}
if (isMacintosh && forkOpts.env) {
// Unset `DYLD_LIBRARY_PATH`, as it leads to process crashes
// See https://github.com/microsoft/vscode/issues/105848

View file

@ -275,7 +275,7 @@ class WorkspaceProvider implements IWorkspaceProvider {
static QUERY_PARAM_PAYLOAD = 'payload';
readonly trusted = undefined;
readonly trusted = true;
constructor(
public readonly workspace: IWorkspace,

View file

@ -15,7 +15,6 @@ import { URI } from 'vs/base/common/uri';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { EditorOpenContext } from 'vs/platform/editor/common/editor';
import { ILogService } from 'vs/platform/log/common/log';
import { IExternalOpener, IExternalUriResolver, IOpener, IOpenerService, IResolvedExternalUri, IValidator, matchesScheme, OpenOptions, ResolveExternalUriOptions } from 'vs/platform/opener/common/opener';
class CommandOpener implements IOpener {
@ -106,8 +105,7 @@ export class OpenerService implements IOpenerService {
constructor(
@ICodeEditorService editorService: ICodeEditorService,
@ICommandService commandService: ICommandService,
@ILogService private logService: ILogService
@ICommandService commandService: ICommandService
) {
// Default external opener is going through window.open()
this._defaultExternalOpener = {
@ -169,7 +167,6 @@ export class OpenerService implements IOpenerService {
const targetURI = typeof target === 'string' ? URI.parse(target) : target;
// validate against the original URI that this URI resolves to, if one exists
const validationTarget = this._resolvedUriTargets.get(targetURI) ?? targetURI;
this.logService.trace(`OpenerService#open: ${targetURI.authority} validating via ${validationTarget.authority}`);
for (const validator of this._validators) {
if (!(await validator.shouldOpen(validationTarget))) {
return false;
@ -192,7 +189,6 @@ export class OpenerService implements IOpenerService {
const result = await resolver.resolveExternalUri(resource, options);
if (result) {
if (!this._resolvedUriTargets.has(result.resolved)) {
this.logService.trace(`OpenerService#resolveExternalUri: ${resource.authority} resolved to ${result.resolved.authority}`);
this._resolvedUriTargets.set(result.resolved, resource);
}
return result;

View file

@ -1140,15 +1140,15 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
const clonedOptions = { ...options };
clonedOptions.inDiffEditor = true;
clonedOptions.automaticLayout = false;
clonedOptions.scrollbar = clonedOptions.scrollbar || {};
// Clone scrollbar options before changing them
clonedOptions.scrollbar = { ...(clonedOptions.scrollbar || {}) };
clonedOptions.scrollbar.vertical = 'visible';
clonedOptions.folding = false;
clonedOptions.codeLens = this._diffCodeLens;
clonedOptions.fixedOverflowWidgets = true;
// clonedOptions.lineDecorationsWidth = '2ch';
if (!clonedOptions.minimap) {
clonedOptions.minimap = {};
}
// Clone minimap options before changing them
clonedOptions.minimap = { ...(clonedOptions.minimap || {}) };
clonedOptions.minimap.enabled = false;
return clonedOptions;
}

View file

@ -43,7 +43,6 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService
import { StandaloneThemeServiceImpl } from 'vs/editor/standalone/browser/standaloneThemeServiceImpl';
import { splitLines } from 'vs/base/common/strings';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ILogService } from 'vs/platform/log/common/log';
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
@ -57,7 +56,7 @@ function withAllStandaloneServices<T extends IEditor>(domElement: HTMLElement, o
}
if (!services.has(IOpenerService)) {
services.set(IOpenerService, new OpenerService(services.get(ICodeEditorService), services.get(ICommandService), services.get(ILogService)));
services.set(IOpenerService, new OpenerService(services.get(ICodeEditorService), services.get(ICommandService)));
}
let result = callback(services);

View file

@ -8,7 +8,6 @@ import { URI } from 'vs/base/common/uri';
import { OpenerService } from 'vs/editor/browser/services/openerService';
import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices';
import { CommandsRegistry, ICommandService, NullCommandService } from 'vs/platform/commands/common/commands';
import { NullLogService } from 'vs/platform/log/common/log';
import { matchesScheme } from 'vs/platform/opener/common/opener';
suite('OpenerService', function () {
@ -31,13 +30,13 @@ suite('OpenerService', function () {
});
test('delegate to editorService, scheme:///fff', async function () {
const openerService = new OpenerService(editorService, NullCommandService, new NullLogService());
const openerService = new OpenerService(editorService, NullCommandService);
await openerService.open(URI.parse('another:///somepath'));
assert.equal(editorService.lastInput!.options!.selection, undefined);
});
test('delegate to editorService, scheme:///fff#L123', async function () {
const openerService = new OpenerService(editorService, NullCommandService, new NullLogService());
const openerService = new OpenerService(editorService, NullCommandService);
await openerService.open(URI.parse('file:///somepath#L23'));
assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23);
@ -59,7 +58,7 @@ suite('OpenerService', function () {
});
test('delegate to editorService, scheme:///fff#123,123', async function () {
const openerService = new OpenerService(editorService, NullCommandService, new NullLogService());
const openerService = new OpenerService(editorService, NullCommandService);
await openerService.open(URI.parse('file:///somepath#23'));
assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23);
@ -77,7 +76,7 @@ suite('OpenerService', function () {
});
test('delegate to commandsService, command:someid', async function () {
const openerService = new OpenerService(editorService, commandService, new NullLogService());
const openerService = new OpenerService(editorService, commandService);
const id = `aCommand${Math.random()}`;
CommandsRegistry.registerCommand(id, function () { });
@ -99,7 +98,7 @@ suite('OpenerService', function () {
});
test('links are protected by validators', async function () {
const openerService = new OpenerService(editorService, commandService, new NullLogService());
const openerService = new OpenerService(editorService, commandService);
openerService.registerValidator({ shouldOpen: () => Promise.resolve(false) });
@ -110,7 +109,7 @@ suite('OpenerService', function () {
});
test('links validated by validators go to openers', async function () {
const openerService = new OpenerService(editorService, commandService, new NullLogService());
const openerService = new OpenerService(editorService, commandService);
openerService.registerValidator({ shouldOpen: () => Promise.resolve(true) });
@ -129,7 +128,7 @@ suite('OpenerService', function () {
});
test('links validated by multiple validators', async function () {
const openerService = new OpenerService(editorService, commandService, new NullLogService());
const openerService = new OpenerService(editorService, commandService);
let v1 = 0;
openerService.registerValidator({
@ -166,7 +165,7 @@ suite('OpenerService', function () {
});
test('links invalidated by first validator do not continue validating', async function () {
const openerService = new OpenerService(editorService, commandService, new NullLogService());
const openerService = new OpenerService(editorService, commandService);
let v1 = 0;
openerService.registerValidator({

View file

@ -61,9 +61,7 @@ export class NsfwWatcherService extends Disposable implements IWatcherService {
});
// Logging
if (this.verboseLogging) {
this.log(`Start watching: [${rootsToStartWatching.map(r => r.path).join(',')}]\nStop watching: [${rootsToStopWatching.join(',')}]`);
}
this.debug(`Start watching: [${rootsToStartWatching.map(r => r.path).join(',')}]\nStop watching: [${rootsToStopWatching.join(',')}]`);
// Stop watching some roots
rootsToStopWatching.forEach(root => {
@ -133,9 +131,7 @@ export class NsfwWatcherService extends Disposable implements IWatcherService {
}
}
if (this.verboseLogging) {
this.log(`Start watching with nsfw: ${request.path}`);
}
this.debug(`Start watching with nsfw: ${request.path}`);
nsfw(request.path, events => {
for (const e of events) {
@ -249,4 +245,8 @@ export class NsfwWatcherService extends Disposable implements IWatcherService {
private error(message: string) {
this._onDidLogMessage.fire({ type: 'error', message: `[File Watcher (nsfw)] ` + message });
}
private debug(message: string) {
this._onDidLogMessage.fire({ type: 'debug', message: `[File Watcher (chokidar)] ` + message });
}
}

View file

@ -146,9 +146,7 @@ export class ChokidarWatcherService extends Disposable implements IWatcherServic
this.warn(`Watcher basePath does not match version on disk and was corrected (original: ${basePath}, real: ${realBasePath})`);
}
if (this.verboseLogging) {
this.log(`Start watching with chokidar: ${realBasePath}, excludes: ${excludes.join(',')}, usePolling: ${usePolling ? 'true, interval ' + pollingInterval : 'false'}`);
}
this.debug(`Start watching with chokidar: ${realBasePath}, excludes: ${excludes.join(',')}, usePolling: ${usePolling ? 'true, interval ' + pollingInterval : 'false'}`);
let chokidarWatcher: chokidar.FSWatcher | null = chokidar.watch(realBasePath, watcherOpts);
this._watcherCount++;
@ -301,6 +299,10 @@ export class ChokidarWatcherService extends Disposable implements IWatcherServic
this._onDidLogMessage.fire({ type: 'trace', message: `[File Watcher (chokidar)] ` + message });
}
private debug(message: string) {
this._onDidLogMessage.fire({ type: 'debug', message: `[File Watcher (chokidar)] ` + message });
}
private warn(message: string) {
this._onDidLogMessage.fire({ type: 'warn', message: `[File Watcher (chokidar)] ` + message });
}

View file

@ -13,7 +13,7 @@ export interface IDiskFileChange {
}
export interface ILogMessage {
type: 'trace' | 'warn' | 'error';
type: 'trace' | 'warn' | 'error' | 'info' | 'debug';
message: string;
}

View file

@ -3,19 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { Event } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IWorkspace, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
export const WORKSPACE_TRUST_ENABLED = 'workspace.trustEnabled';
export const WORKSPACE_TRUST_URI = URI.parse('workspaceTrust:/Trusted Workspaces');
export enum WorkspaceTrustScope {
Local = 0,
@ -40,11 +31,6 @@ export function workspaceTrustStateToString(trustState: WorkspaceTrustState) {
}
}
export const WorkspaceTrustContext = {
PendingRequest: new RawContextKey<boolean>('workspaceTrustPendingRequest', false),
TrustState: new RawContextKey<WorkspaceTrustState>('workspaceTrustState', WorkspaceTrustState.Unknown)
};
export interface IWorkspaceTrustModel {
readonly onDidChangeTrustState: Event<void>;
@ -86,290 +72,11 @@ export interface IWorkspaceTrustService {
getWorkspaceTrustState(): WorkspaceTrustState;
isWorkspaceTrustEnabled(): boolean;
requireWorkspaceTrust(request: IWorkspaceTrustRequest): Promise<WorkspaceTrustState>;
resetWorkspaceTrust(): Promise<WorkspaceTrustState>;
}
interface IWorkspaceTrustStateInfo {
export interface IWorkspaceTrustStateInfo {
localFolders: { uri: string, trustState: WorkspaceTrustState }[]
// Removing complexity of remote items
//trustedRemoteItems: { uri: string }[]
}
export const WORKSPACE_TRUST_STORAGE_KEY = 'content.trust.model.key';
export class WorkspaceTrustModel extends Disposable implements IWorkspaceTrustModel {
private storageKey = WORKSPACE_TRUST_STORAGE_KEY;
private trustStateInfo: IWorkspaceTrustStateInfo;
private readonly _onDidChangeTrustState = this._register(new Emitter<void>());
readonly onDidChangeTrustState = this._onDidChangeTrustState.event;
constructor(
private readonly storageService: IStorageService
) {
super();
this.trustStateInfo = this.loadTrustInfo();
this._register(this.storageService.onDidChangeValue(changeEvent => {
if (changeEvent.key === this.storageKey) {
this.onDidStorageChange();
}
}));
}
private loadTrustInfo(): IWorkspaceTrustStateInfo {
const infoAsString = this.storageService.get(this.storageKey, StorageScope.GLOBAL);
let result: IWorkspaceTrustStateInfo | undefined;
try {
if (infoAsString) {
result = JSON.parse(infoAsString);
}
} catch { }
if (!result) {
result = {
localFolders: [],
//trustedRemoteItems: []
};
}
if (!result.localFolders) {
result.localFolders = [];
}
// if (!result.trustedRemoteItems) {
// result.trustedRemoteItems = [];
// }
return result;
}
private saveTrustInfo(): void {
this.storageService.store(this.storageKey, JSON.stringify(this.trustStateInfo), StorageScope.GLOBAL, StorageTarget.MACHINE);
}
private onDidStorageChange(): void {
this.trustStateInfo = this.loadTrustInfo();
this._onDidChangeTrustState.fire();
}
setFolderTrustState(folder: URI, trustState: WorkspaceTrustState): void {
let changed = false;
if (trustState === WorkspaceTrustState.Unknown) {
const before = this.trustStateInfo.localFolders.length;
this.trustStateInfo.localFolders = this.trustStateInfo.localFolders.filter(info => info.uri !== folder.toString());
if (this.trustStateInfo.localFolders.length !== before) {
changed = true;
}
} else {
let found = false;
for (const trustInfo of this.trustStateInfo.localFolders) {
if (trustInfo.uri === folder.toString()) {
found = true;
if (trustInfo.trustState !== trustState) {
trustInfo.trustState = trustState;
changed = true;
}
}
}
if (!found) {
this.trustStateInfo.localFolders.push({ uri: folder.toString(), trustState });
changed = true;
}
}
if (changed) {
this.saveTrustInfo();
}
}
getFolderTrustState(folder: URI): WorkspaceTrustState {
for (const trustInfo of this.trustStateInfo.localFolders) {
if (trustInfo.uri === folder.toString()) {
return trustInfo.trustState;
}
}
return WorkspaceTrustState.Unknown;
}
}
export class WorkspaceTrustRequestModel extends Disposable implements IWorkspaceTrustRequestModel {
trustRequest: IWorkspaceTrustRequest | undefined;
_onDidInitiateRequest = this._register(new Emitter<void>());
onDidInitiateRequest: Event<void> = this._onDidInitiateRequest.event;
_onDidCompleteRequest = this._register(new Emitter<WorkspaceTrustState | undefined>());
onDidCompleteRequest = this._onDidCompleteRequest.event;
initiateRequest(request: IWorkspaceTrustRequest): void {
if (this.trustRequest && (!request.immediate || this.trustRequest.immediate)) {
return;
}
this.trustRequest = request;
this._onDidInitiateRequest.fire();
}
completeRequest(trustState?: WorkspaceTrustState): void {
this.trustRequest = undefined;
this._onDidCompleteRequest.fire(trustState);
}
}
export class WorkspaceTrustService extends Disposable implements IWorkspaceTrustService {
_serviceBrand: undefined;
private readonly dataModel: IWorkspaceTrustModel;
readonly requestModel: IWorkspaceTrustRequestModel;
private readonly _onDidChangeTrustState = this._register(new Emitter<WorkspaceTrustStateChangeEvent>());
readonly onDidChangeTrustState = this._onDidChangeTrustState.event;
private _currentTrustState: WorkspaceTrustState = WorkspaceTrustState.Unknown;
private _inFlightResolver?: (trustState: WorkspaceTrustState) => void;
private _trustRequestPromise?: Promise<WorkspaceTrustState>;
private _workspace: IWorkspace;
private readonly _ctxWorkspaceTrustState: IContextKey<WorkspaceTrustState>;
private readonly _ctxWorkspaceTrustPendingRequest: IContextKey<boolean>;
constructor(
@IStorageService private readonly storageService: IStorageService,
@IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService,
@IConfigurationService readonly configurationService: IConfigurationService,
@IContextKeyService readonly contextKeyService: IContextKeyService
) {
super();
this.dataModel = this._register(new WorkspaceTrustModel(this.storageService));
this.requestModel = this._register(new WorkspaceTrustRequestModel());
this._workspace = this.workspaceService.getWorkspace();
this._currentTrustState = this.calculateWorkspaceTrustState();
this._register(this.dataModel.onDidChangeTrustState(() => this.currentTrustState = this.calculateWorkspaceTrustState()));
this._register(this.requestModel.onDidCompleteRequest((trustState) => this.onTrustRequestCompleted(trustState)));
this._ctxWorkspaceTrustState = WorkspaceTrustContext.TrustState.bindTo(contextKeyService);
this._ctxWorkspaceTrustPendingRequest = WorkspaceTrustContext.PendingRequest.bindTo(contextKeyService);
this._ctxWorkspaceTrustState.set(this.currentTrustState);
}
private get currentTrustState(): WorkspaceTrustState {
return this._currentTrustState;
}
private set currentTrustState(trustState: WorkspaceTrustState) {
if (this._currentTrustState === trustState) { return; }
const previousState = this._currentTrustState;
this._currentTrustState = trustState;
this._onDidChangeTrustState.fire({ previousTrustState: previousState, currentTrustState: this._currentTrustState });
}
private calculateWorkspaceTrustState(): WorkspaceTrustState {
if (!this.isWorkspaceTrustEnabled()) {
return WorkspaceTrustState.Trusted;
}
if (this.workspaceService.getWorkbenchState() === WorkbenchState.EMPTY) {
return WorkspaceTrustState.Trusted;
}
let state = undefined;
for (const folder of this._workspace.folders) {
const folderTrust = this.dataModel.getFolderTrustState(folder.uri);
switch (folderTrust) {
case WorkspaceTrustState.Untrusted:
return WorkspaceTrustState.Untrusted;
case WorkspaceTrustState.Unknown:
state = folderTrust;
break;
case WorkspaceTrustState.Trusted:
if (state === undefined) {
state = folderTrust;
}
break;
}
}
return state ?? WorkspaceTrustState.Unknown;
}
private onTrustRequestCompleted(trustState?: WorkspaceTrustState): void {
if (this._inFlightResolver) {
this._inFlightResolver(trustState === undefined ? this.currentTrustState : trustState);
}
this._inFlightResolver = undefined;
this._trustRequestPromise = undefined;
if (trustState === undefined) {
return;
}
this._workspace.folders.forEach(folder => {
this.dataModel.setFolderTrustState(folder.uri, trustState);
});
this._ctxWorkspaceTrustPendingRequest.set(false);
this._ctxWorkspaceTrustState.set(trustState);
}
getWorkspaceTrustState(): WorkspaceTrustState {
return this.currentTrustState;
}
isWorkspaceTrustEnabled(): boolean {
return this.configurationService.getValue<boolean>(WORKSPACE_TRUST_ENABLED) ?? false;
}
async requireWorkspaceTrust(request?: IWorkspaceTrustRequest): Promise<WorkspaceTrustState> {
if (this.currentTrustState === WorkspaceTrustState.Trusted) {
return this.currentTrustState;
}
if (this.currentTrustState === WorkspaceTrustState.Untrusted && !request?.immediate) {
return this.currentTrustState;
}
if (this._trustRequestPromise) {
if (request?.immediate &&
this.requestModel.trustRequest &&
!this.requestModel.trustRequest.immediate) {
this.requestModel.initiateRequest(request);
}
return this._trustRequestPromise;
}
this._trustRequestPromise = new Promise(resolve => {
this._inFlightResolver = resolve;
});
this.requestModel.initiateRequest(request);
this._ctxWorkspaceTrustPendingRequest.set(true);
return this._trustRequestPromise;
}
async resetWorkspaceTrust(): Promise<WorkspaceTrustState> {
if (this.currentTrustState !== WorkspaceTrustState.Unknown) {
this._workspace.folders.forEach(folder => {
this.dataModel.setFolderTrustState(folder.uri, WorkspaceTrustState.Unknown);
});
}
return Promise.resolve(WorkspaceTrustState.Unknown);
}
}
registerSingleton(IWorkspaceTrustService, WorkspaceTrustService);

View file

@ -21,6 +21,8 @@ export class MainThreadAuthenticationProvider extends Disposable {
private _accounts = new Map<string, string[]>(); // Map account name to session ids
private _sessions = new Map<string, string>(); // Map account id to name
private _hasInitializedSessions = false;
constructor(
private readonly _proxy: ExtHostAuthenticationShape,
public readonly id: string,
@ -33,11 +35,6 @@ export class MainThreadAuthenticationProvider extends Disposable {
) {
super();
}
public async initialize(): Promise<void> {
return this.registerCommandsAndContextMenuItems();
}
public hasSessions(): boolean {
return !!this._sessions.size;
}
@ -83,15 +80,6 @@ export class MainThreadAuthenticationProvider extends Disposable {
quickPick.show();
}
private async registerCommandsAndContextMenuItems(): Promise<void> {
try {
const sessions = await this._proxy.$getSessions(this.id);
sessions.forEach(session => this.registerSession(session));
} catch (_) {
// Ignore
}
}
private registerSession(session: modes.AuthenticationSession) {
this._sessions.set(session.id, session.account.label);
@ -123,7 +111,13 @@ export class MainThreadAuthenticationProvider extends Disposable {
}
async getSessions(): Promise<ReadonlyArray<modes.AuthenticationSession>> {
return this._proxy.$getSessions(this.id);
const sessions = await this._proxy.$getSessions(this.id);
if (!this._hasInitializedSessions) {
sessions.forEach(session => this.registerSession(session));
this._hasInitializedSessions = true;
}
return sessions;
}
async updateSessionItems(event: modes.AuthenticationSessionsChangeEvent): Promise<void> {
@ -195,7 +189,6 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
async $registerAuthenticationProvider(id: string, label: string, supportsMultipleAccounts: boolean): Promise<void> {
const provider = new MainThreadAuthenticationProvider(this._proxy, id, label, supportsMultipleAccounts, this.notificationService, this.storageService, this.quickInputService, this.dialogService);
await provider.initialize();
this.authenticationService.registerAuthenticationProvider(id, provider);
}

View file

@ -51,6 +51,17 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
}
}
/**
* @inheritdoc
*/
$retireTest(extId: string): void {
for (const result of this.resultService.results) {
if (result instanceof LiveTestResult) {
result.retire(extId);
}
}
}
/**
* @inheritdoc
*/

View file

@ -1842,6 +1842,7 @@ export interface MainThreadTestingShape {
$publishDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void;
$updateTestStateInRun(runId: string, testId: string, state: ITestState): void;
$runTests(req: RunTestsRequest, token: CancellationToken): Promise<string>;
$retireTest(extId: string): void;
}
// --- proxy identifiers

View file

@ -170,6 +170,14 @@ export class ExtHostTesting implements ExtHostTestingShape {
collection.addRoot(hierarchy.root, id);
Promise.resolve(hierarchy.discoveredInitialTests).then(() => collection.pushDiff([TestDiffOpType.DeltaDiscoverComplete, -1]));
hierarchy.onDidChangeTest(e => collection.onItemChange(e, id));
hierarchy.onDidInvalidateTest?.(e => {
const internal = collection.getTestByReference(e);
if (!internal) {
console.warn(`Received a TestProvider.onDidInvalidateTest for a test that does not currently exist.`);
} else {
this.proxy.$retireTest(internal.item.extId);
}
});
} catch (e) {
console.error(e);
}

View file

@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files';
import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { CellEditType, ICellEditOperation, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellEditType, ICellEditOperation, notebookDocumentMetadataDefaults, NOTEBOOK_DISPLAY_ORDER } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import type * as vscode from 'vscode';
function es5ClassCompat(target: Function): any {
@ -2851,30 +2851,69 @@ export enum ColorThemeKind {
export class NotebookCellMetadata {
readonly readonly: boolean;
readonly inputCollapsed: boolean;
readonly outputCollapsed: boolean;
constructor(
readonly editable?: boolean,
readonly breakpointMargin?: boolean,
readonly runnable?: boolean,
readonly hasExecutionOrder?: boolean,
readonly executionOrder?: number,
readonly runState?: NotebookCellRunState,
readonly runStartTime?: number,
readonly statusMessage?: string,
readonly lastRunDuration?: number,
readonly inputCollapsed?: boolean,
readonly outputCollapsed?: boolean,
readonly custom?: Record<string, any>,
) { }
[key: string]: unknown;
constructor(readonly: boolean, inputCollapsed: boolean, outputCollapsed: boolean) {
this.readonly = readonly;
this.inputCollapsed = inputCollapsed;
this.outputCollapsed = outputCollapsed;
}
with(change: Partial<{ readonly: boolean, inputCollapsed: boolean, outputCollapsed: boolean, [key: string]: unknown }>): NotebookCellMetadata {
const thisAndChange = { ...this, ...change };
const res = new NotebookCellMetadata(thisAndChange.readonly, thisAndChange.inputCollapsed, thisAndChange.outputCollapsed);
for (const key in change) {
if (Object.prototype.hasOwnProperty.call(change, key)) {
res[key] = change[key];
}
}
return res;
with(change: Partial<Omit<NotebookCellMetadata, 'with'>>): NotebookCellMetadata {
return new NotebookCellMetadata(
change.editable ?? this.editable,
change.breakpointMargin ?? this.breakpointMargin,
change.runnable ?? this.runnable,
change.hasExecutionOrder ?? this.hasExecutionOrder,
change.executionOrder ?? this.executionOrder,
change.runState ?? this.runState,
change.runStartTime ?? this.runStartTime,
change.statusMessage ?? this.statusMessage,
change.lastRunDuration ?? this.lastRunDuration,
change.inputCollapsed ?? this.inputCollapsed,
change.outputCollapsed ?? this.outputCollapsed,
change.custom ?? this.custom
);
}
}
export class NotebookDocumentMetadata {
constructor(
readonly editable: boolean = true,
readonly runnable: boolean = true,
readonly cellEditable: boolean = true,
readonly cellRunnable: boolean = true,
readonly cellHasExecutionOrder: boolean = true,
readonly displayOrder: vscode.GlobPattern[] = NOTEBOOK_DISPLAY_ORDER,
readonly custom: { [key: string]: any; } = {},
readonly runState: NotebookRunState = NotebookRunState.Idle,
readonly trusted: boolean = true,
readonly languages: string[] = [],
) { }
with(change: Partial<Omit<NotebookDocumentMetadata, 'with'>>) {
return new NotebookDocumentMetadata(
change.editable ?? this.editable,
change.runnable ?? this.runnable,
change.cellEditable ?? this.cellEditable,
change.cellRunnable ?? this.cellRunnable,
change.cellHasExecutionOrder ?? this.cellHasExecutionOrder,
change.displayOrder ?? this.displayOrder,
change.custom ?? this.custom,
change.runState ?? this.runState,
change.trusted ?? this.trusted,
change.languages ?? this.languages,
);
}
}
export class NotebookCellOutputItem {

View file

@ -23,7 +23,7 @@ import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWorkspacesService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
import { WORKSPACE_TRUST_ENABLED, WORKSPACE_TRUST_URI } from 'vs/platform/workspace/common/workspaceTrust';
import { WORKSPACE_TRUST_ENABLED, WORKSPACE_TRUST_URI } from 'vs/workbench/services/workspaces/common/workspaceTrust';
export class OpenFileAction extends Action {
@ -255,8 +255,9 @@ class WorkspaceTrustManageAction extends Action2 {
constructor() {
super({
id: 'workbench.action.manageTrust',
title: { value: nls.localize('resetTrustAction', "Manage Trusted Workspaces"), original: 'Manage Trusted Workspaces' },
title: { value: nls.localize('manageTrustAction', "Manage Workspace Trust"), original: 'Manage Workspace Trust' },
precondition: ContextKeyExpr.equals(`config.${WORKSPACE_TRUST_ENABLED}`, true),
category: nls.localize('workspacesCategory', "Workspaces"),
f1: true,
});
}

View file

@ -259,6 +259,11 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
}
});
if (providers.length && !menus.length) {
const noAccountsAvailableAction = disposables.add(new Action('noAccountsAvailable', localize('noAccounts', "You are not signed in to any accounts"), undefined, false));
menus.push(noAccountsAvailableAction);
}
if (menus.length && otherCommands.length) {
menus.push(disposables.add(new Separator()));
}

View file

@ -199,8 +199,6 @@ export class ActivityActionViewItem extends BaseActionViewItem {
this.container = container;
// Make the container tab-able for keyboard navigation
this.container.tabIndex = 0;
this.container.setAttribute('role', 'tab');
// Try hard to prevent keyboard only focus feedback when using mouse
@ -647,10 +645,6 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
});
}
focus(): void {
this.container.focus();
}
protected updateChecked(): void {
if (this.getAction().checked) {
this.container.classList.add('checked');

View file

@ -427,15 +427,9 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker {
}
async _revealElement(element: IFileStat | IWorkspaceFolder, options: IEditorOptions, sideBySide: boolean): Promise<boolean> {
let resource: URI | undefined;
if (isWorkspaceFolder(element)) {
resource = element.uri;
} else if (!element.isDirectory) {
resource = element.resource;
}
if (resource) {
if (!isWorkspaceFolder(element) && element.isFile) {
this._onWillPickElement.fire();
await this._editorService.openEditor({ resource, options }, sideBySide ? SIDE_GROUP : undefined);
await this._editorService.openEditor({ resource: element.resource, options }, sideBySide ? SIDE_GROUP : undefined);
return true;
}
return false;

View file

@ -192,7 +192,8 @@ export abstract class ViewPane extends Pane implements IView {
return this._titleDescription;
}
private readonly menuActions: ViewMenuActions;
readonly menuActions: ViewMenuActions;
private progressBar!: ProgressBar;
private progressIndicator!: IProgressIndicator;
@ -478,7 +479,7 @@ export abstract class ViewPane extends Pane implements IView {
private setActions(): void {
if (this.toolbar) {
this.toolbar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions()));
this.toolbar.setActions(prepareActions(this.menuActions.getPrimaryActions()), prepareActions(this.menuActions.getSecondaryActions()));
this.toolbar.context = this.getActionsContext();
}
}
@ -496,18 +497,6 @@ export abstract class ViewPane extends Pane implements IView {
this._onDidChangeTitleArea.fire();
}
getActions(): IAction[] {
return this.menuActions.getPrimaryActions();
}
getSecondaryActions(): IAction[] {
return this.menuActions.getSecondaryActions();
}
getContextMenuActions(): IAction[] {
return this.menuActions.getContextMenuActions();
}
getActionViewItem(action: IAction): IActionViewItem | undefined {
return createActionViewItem(this.instantiationService, action);
}

View file

@ -583,13 +583,13 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
const result = [];
result.push(...this.menuActions.getPrimaryActions());
if (this.isViewMergedWithContainer()) {
result.push(...this.paneItems[0].pane.getActions());
result.push(...this.paneItems[0].pane.menuActions.getPrimaryActions());
}
return result;
}
getSecondaryActions(): IAction[] {
const viewPaneActions = this.isViewMergedWithContainer() ? this.paneItems[0].pane.getSecondaryActions() : [];
const viewPaneActions = this.isViewMergedWithContainer() ? this.paneItems[0].pane.menuActions.getSecondaryActions() : [];
let menuActions = this.menuActions.getSecondaryActions();
const viewsSubmenuActionIndex = menuActions.findIndex(action => action instanceof SubmenuItemAction && action.item.submenu === ViewsSubMenu);
@ -768,7 +768,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
event.stopPropagation();
event.preventDefault();
const actions: IAction[] = viewPane.getContextMenuActions();
const actions: IAction[] = viewPane.menuActions.getContextMenuActions();
let anchor: { x: number, y: number } = { x: event.posx, y: event.posy };
this.contextMenuService.showContextMenu({

View file

@ -20,7 +20,7 @@ import {
} from 'vs/workbench/contrib/debug/common/debug';
import { DebugToolBar } from 'vs/workbench/contrib/debug/browser/debugToolBar';
import { DebugService } from 'vs/workbench/contrib/debug/browser/debugService';
import { registerCommands, ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_ID, RESTART_SESSION_ID, TERMINATE_THREAD_ID, STEP_OVER_ID, STEP_INTO_ID, STEP_OUT_ID, PAUSE_ID, DISCONNECT_ID, STOP_ID, RESTART_FRAME_ID, CONTINUE_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, RESTART_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, STEP_OUT_LABEL, PAUSE_LABEL, DISCONNECT_LABEL, STOP_LABEL, CONTINUE_LABEL, DEBUG_START_LABEL, DEBUG_START_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_RUN_COMMAND_ID, EDIT_EXPRESSION_COMMAND_ID, REMOVE_EXPRESSION_COMMAND_ID } from 'vs/workbench/contrib/debug/browser/debugCommands';
import { registerCommands, ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_ID, RESTART_SESSION_ID, TERMINATE_THREAD_ID, STEP_OVER_ID, STEP_INTO_ID, STEP_OUT_ID, PAUSE_ID, DISCONNECT_ID, STOP_ID, RESTART_FRAME_ID, CONTINUE_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, RESTART_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, STEP_OUT_LABEL, PAUSE_LABEL, DISCONNECT_LABEL, STOP_LABEL, CONTINUE_LABEL, DEBUG_START_LABEL, DEBUG_START_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_RUN_COMMAND_ID, EDIT_EXPRESSION_COMMAND_ID, REMOVE_EXPRESSION_COMMAND_ID, SELECT_AND_START_ID, SELECT_AND_START_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands';
import { StatusBarColorProvider } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider';
import { IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views';
import { isMacintosh, isWeb } from 'vs/base/common/platform';
@ -123,6 +123,8 @@ function registerCommandsAndActions(): void {
registerDebugCommandPaletteItem(TOGGLE_INLINE_BREAKPOINT_ID, nls.localize('inlineBreakpoint', "Inline Breakpoint"));
registerDebugCommandPaletteItem(DEBUG_START_COMMAND_ID, DEBUG_START_LABEL, ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing))));
registerDebugCommandPaletteItem(DEBUG_RUN_COMMAND_ID, DEBUG_RUN_LABEL, ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing))));
registerDebugCommandPaletteItem(SELECT_AND_START_ID, SELECT_AND_START_LABEL, ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing))));
// Debug callstack context menu
const registerDebugViewMenuItem = (menuId: MenuId, id: string, title: string, order: number, when?: ContextKeyExpression, precondition?: ContextKeyExpression, group = 'navigation') => {

View file

@ -165,6 +165,10 @@ export class ReplFilterActionViewItem extends BaseActionViewItem {
return this.filterInputBox.getHistory();
}
get trapsArrowNavigation(): boolean {
return true;
}
private clearFilterText(): void {
this.filterInputBox.value = '';
}

View file

@ -32,7 +32,7 @@ import { timeout } from 'vs/base/common/async';
import { TestExtensionService } from 'vs/workbench/test/common/workbenchTestServices';
import { OS } from 'vs/base/common/platform';
import { IWorkspaceTrustService } from 'vs/platform/workspace/common/workspaceTrust';
import { TestWorkspaceTrustService } from 'vs/platform/workspace/test/common/testWorkspaceTrust';
import { TestWorkspaceTrustService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService';
interface ExperimentSettings {
enabled?: boolean;

View file

@ -13,7 +13,7 @@ import { IExtensionManagementServerService } from 'vs/workbench/services/extensi
import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { ILabelService } from 'vs/platform/label/common/label';
import { extensionButtonProminentBackground, extensionButtonProminentForeground, ExtensionToolTipAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { IThemeService, IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IThemeService, IColorTheme, ThemeIcon, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { EXTENSION_BADGE_REMOTE_BACKGROUND, EXTENSION_BADGE_REMOTE_FOREGROUND } from 'vs/workbench/common/theme';
import { Emitter, Event } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@ -21,6 +21,7 @@ import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
import { installCountIcon, ratingIcon, remoteIcon, starEmptyIcon, starFullIcon, starHalfIcon, syncIgnoredIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons';
import { registerColor } from 'vs/platform/theme/common/colorRegistry';
export abstract class ExtensionWidget extends Disposable implements IExtensionContainer {
private _extension: IExtension | null = null;
@ -368,3 +369,13 @@ export class SyncIgnoredWidget extends ExtensionWidget {
this.element.classList.toggle('hide', !(this.extension && this.extension.state === ExtensionState.Installed && this.userDataAutoSyncEnablementService.isEnabled() && this.extensionsWorkbenchService.isExtensionIgnoredToSync(this.extension)));
}
}
// Rating icon
export const extensionRatingIconColor = registerColor('extensionIcon.starForeground', { light: '#DF6100', dark: '#FF8E00', hc: '#FF8E00' }, localize('extensionIconStarForeground', "The icon color for extension ratings."), true);
registerThemingParticipant((theme, collector) => {
const extensionRatingIcon = theme.getColor(extensionRatingIconColor);
if (extensionRatingIcon) {
collector.addRule(`.extension-ratings .codicon-extensions-star-full, .extension-ratings .codicon-extensions-star-half { color: ${extensionRatingIcon}; }`);
}
});

View file

@ -27,12 +27,6 @@
margin-left: 0;
}
/* TODO @misolori make this a color token */
.extension-ratings .codicon-extensions-star-full,
.extension-ratings .codicon-extensions-star-half {
color: #FF8E00 !important;
}
.extension-install-count .codicon,
.extension-action.codicon-extensions-info-message,
.extension-action.codicon-extensions-warning-message,

View file

@ -56,7 +56,7 @@ import { UserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
import { IWorkspaceTrustService } from 'vs/platform/workspace/common/workspaceTrust';
import { TestWorkspaceTrustService } from 'vs/platform/workspace/test/common/testWorkspaceTrust';
import { TestWorkspaceTrustService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService';
let instantiationService: TestInstantiationService;
let installEvent: Emitter<InstallExtensionEvent>,

View file

@ -283,6 +283,10 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem {
}
}
get trapsArrowNavigation(): boolean {
return true;
}
private clearFilterText(): void {
if (this.filterInputBox) {
this.filterInputBox.value = '';

View file

@ -23,7 +23,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { BareFontInfo } from 'vs/editor/common/config/fontInfo';
import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser';
import { ICellOutputViewModel, IDisplayOutputLayoutUpdateRequest, IGenericCellViewModel, IInsetRenderOutput, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellEditState, ICellOutputViewModel, IDisplayOutputLayoutUpdateRequest, IGenericCellViewModel, IInsetRenderOutput, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { DiffSide, DIFF_CELL_MARGIN, IDiffCellInfo, INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser';
import { Emitter } from 'vs/base/common/event';
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
@ -134,6 +134,19 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
}
}
setMarkdownCellEditState(cellId: string, editState: CellEditState): void {
// throw new Error('Method not implemented.');
}
markdownCellDragStart(cellId: string, position: { clientY: number }): void {
// throw new Error('Method not implemented.');
}
markdownCellDrag(cellId: string, position: { clientY: number }): void {
// throw new Error('Method not implemented.');
}
markdownCellDragEnd(cellId: string, position: { clientY: number }): void {
// throw new Error('Method not implemented.');
}
protected createEditor(parent: HTMLElement): void {
this._rootElement = DOM.append(parent, DOM.$('.notebook-text-diff-editor'));
this._overflowContainer = document.createElement('div');
@ -589,6 +602,10 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
});
}
updateMarkdownCellHeight() {
// TODO
}
getCellByInfo(cellInfo: IDiffCellInfo): IGenericCellViewModel {
return cellInfo.diffElement.getCellByUri(cellInfo.cellUri);
}

View file

@ -38,6 +38,12 @@ export interface INotebookRendererContribution {
readonly [NotebookRendererContribution.entrypoint]: string;
}
export interface INotebookMarkdownRendererContribution {
readonly [NotebookRendererContribution.id]?: string;
readonly [NotebookRendererContribution.viewType]?: string;
readonly [NotebookRendererContribution.entrypoint]: string;
}
const notebookProviderContribution: IJSONSchema = {
description: nls.localize('contributes.notebook.provider', 'Contributes notebook document provider.'),
type: 'array',
@ -144,3 +150,9 @@ export const notebookRendererExtensionPoint = ExtensionsRegistry.registerExtensi
extensionPoint: 'notebookOutputRenderer',
jsonSchema: notebookRendererContribution
});
export const notebookMarkdownRendererExtensionPoint = ExtensionsRegistry.registerExtensionPoint<INotebookMarkdownRendererContribution[]>(
{
extensionPoint: 'notebookMarkdownRenderer',
jsonSchema: notebookRendererContribution //
});

View file

@ -164,6 +164,11 @@ export interface ICommonNotebookEditor {
focusNotebookCell(cell: IGenericCellViewModel, focus: 'editor' | 'container' | 'output'): void;
focusNextNotebookCell(cell: IGenericCellViewModel, focus: 'editor' | 'container' | 'output'): void;
updateOutputHeight(cellInfo: ICommonCellInfo, output: IDisplayOutputViewModel, height: number, isInit: boolean): void;
updateMarkdownCellHeight(cellId: string, height: number, isInit: boolean): void;
setMarkdownCellEditState(cellId: string, editState: CellEditState): void;
markdownCellDragStart(cellId: string, position: { clientY: number }): void;
markdownCellDrag(cellId: string, position: { clientY: number }): void;
markdownCellDragEnd(cellId: string, position: { clientY: number, ctrlKey: boolean, altKey: boolean }): void;
}
//#endregion
@ -459,6 +464,10 @@ export interface INotebookEditor extends IEditor, ICommonNotebookEditor {
*/
layoutNotebookCell(cell: ICellViewModel, height: number): Promise<void>;
createMarkdownPreview(cell: ICellViewModel): Promise<void>;
hideMarkdownPreview(cell: ICellViewModel): Promise<void>;
removeMarkdownPreview(cell: ICellViewModel): Promise<void>;
/**
* Render the output in webview layer
*/
@ -681,6 +690,7 @@ export interface BaseCellRenderTemplate {
export interface MarkdownCellRenderTemplate extends BaseCellRenderTemplate {
editorContainer: HTMLElement;
foldingIndicator: HTMLElement;
focusIndicatorBottom: HTMLElement;
currentEditor?: ICodeEditor;
}

View file

@ -64,6 +64,7 @@ import { configureKernelIcon, errorStateIcon, successStateIcon } from 'vs/workbe
import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugColors';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { extname } from 'vs/base/common/resources';
import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel';
const $ = DOM.$;
@ -1016,6 +1017,19 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
this._updateForMetadata();
}));
const useRenderer = this.configurationService.getValue<string>('notebook.experimental.useMarkdownRenderer');
if (useRenderer) {
await this._resolveWebview();
await this._webview!.initializeMarkdown(this.viewModel.viewCells
.filter(cell => cell.cellKind === CellKind.Markdown)
.map(cell => ({ cellId: cell.id, content: cell.getText() }))
// TODO: look at cell position cache instead of just getting first five cells
.slice(0, 5));
}
// restore view states, including contributions
{
@ -1105,6 +1119,18 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
this._webview?.updateViewScrollTop(-scrollTop, false, updateItems);
}
}
if (this._webview?.markdownPreviewMapping) {
const updateItems: { id: string, top: number }[] = [];
this._webview!.markdownPreviewMapping.forEach(cellId => {
const cell = this.viewModel?.viewCells.find(cell => cell.id === cellId);
if (cell) {
const cellTop = this._list.getAbsoluteTopOfElement(cell);
updateItems.push({ id: cellId, top: cellTop });
}
});
this._webview?.updateMarkdownScrollTop(updateItems);
}
});
}));
@ -1957,6 +1983,41 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
this._list.triggerScrollFromMouseWheelEvent(event);
}
async createMarkdownPreview(cell: MarkdownCellViewModel) {
if (!this._webview) {
return;
}
await this._resolveWebview();
const cellTop = this._list.getAbsoluteTopOfElement(cell);
if (this._webview.markdownPreviewMapping.has(cell.id)) {
await this._webview!.showMarkdownPreview(cell.id, cell.getText(), cellTop);
} else {
await this._webview!.createMarkdownPreview(cell.id, cell.getText(), cellTop);
}
}
async hideMarkdownPreview(cell: MarkdownCellViewModel) {
if (!this._webview) {
return;
}
await this._resolveWebview();
await this._webview!.hideMarkdownPreview(cell.id);
}
async removeMarkdownPreview(cell: MarkdownCellViewModel) {
if (!this._webview) {
return;
}
await this._resolveWebview();
await this._webview!.removeMarkdownPreview(cell.id);
}
async createInset(cell: CodeCellViewModel, output: IInsetRenderOutput, offset: number): Promise<void> {
this._insetModifyQueueByOutputId.queue(output.source.model.outputId, async () => {
if (!this._webview) {
@ -2040,6 +2101,45 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}
}
updateMarkdownCellHeight(cellId: string, height: number, isInit: boolean) {
const cell = this.viewModel?.viewCells.find(vc => vc.id === cellId);
if (cell && cell instanceof MarkdownCellViewModel) {
cell.renderedMarkdownHeight = height;
}
}
setMarkdownCellEditState(cellId: string, editState: CellEditState): void {
const cell = this.viewModel?.viewCells.find(vc => vc.id === cellId);
if (cell && cell instanceof MarkdownCellViewModel) {
cell.editState = editState;
}
}
markdownCellDragStart(cellId: string, ctx: { clientY: number }): void {
const cell = this.viewModel?.viewCells.find(vc => vc.id === cellId);
if (cell && cell instanceof MarkdownCellViewModel) {
this._dndController?.startExplicitDrag(cell, ctx);
}
}
markdownCellDrag(cellId: string, ctx: { clientY: number }): void {
const cell = this.viewModel?.viewCells.find(vc => vc.id === cellId);
if (cell && cell instanceof MarkdownCellViewModel) {
this._dndController?.explicitDrag(cell, ctx);
}
}
markdownCellDragEnd(cellId: string, ctx: { clientY: number, ctrlKey: boolean, altKey: boolean }): void {
const cell = this.viewModel?.viewCells.find(vc => vc.id === cellId);
if (cell && cell instanceof MarkdownCellViewModel) {
this._dndController?.endExplicitDrag(cell, ctx);
}
}
//#endregion
@ -2295,8 +2395,7 @@ registerThemingParticipant((theme, collector) => {
collector.addRule(`
.monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused .cell-focus-indicator-top:before,
.monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused .cell-focus-indicator-bottom:before,
.monaco-workbench .notebookOverlay .monaco-list:focus-within .markdown-cell-row.focused .cell-inner-container:not(.cell-editor-focus):before,
.monaco-workbench .notebookOverlay .monaco-list:focus-within .markdown-cell-row.focused .cell-inner-container:not(.cell-editor-focus):after {
.monaco-workbench .notebookOverlay .monaco-list:focus-within .markdown-cell-row.focused .cell-inner-container:not(.cell-editor-focus):before {
border-color: ${focusedCellBorderColor} !important;
}`);
@ -2313,8 +2412,7 @@ registerThemingParticipant((theme, collector) => {
collector.addRule(`
.monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused .cell-editor-focus .cell-focus-indicator-top:before,
.monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused .cell-editor-focus .cell-focus-indicator-bottom:before,
.monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused .cell-inner-container.cell-editor-focus:before,
.monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused .cell-inner-container.cell-editor-focus:after {
.monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused .cell-inner-container.cell-editor-focus:before {
border-color: ${selectedCellBorderColor} !important;
}`);

View file

@ -26,13 +26,14 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol';
import { Memento } from 'vs/workbench/common/memento';
import { INotebookEditorContribution, notebookProviderExtensionPoint, notebookRendererExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint';
import { INotebookEditorContribution, notebookMarkdownRendererExtensionPoint, notebookProviderExtensionPoint, notebookRendererExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint';
import { CellEditState, getActiveNotebookEditor, ICellViewModel, INotebookEditor, NotebookEditorOptions, updateEditorTopPadding } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookKernelProviderAssociationRegistry, NotebookViewTypesExtensionRegistry, updateNotebookKernelProvideAssociationSchema } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation';
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellEditType, CellKind, DisplayOrderKey, ICellEditOperation, INotebookDecorationRenderOptions, INotebookKernelInfo2, INotebookKernelProvider, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, notebookDocumentFilterMatch, NotebookEditorPriority, NOTEBOOK_DISPLAY_ORDER, RENDERER_NOT_AVAILABLE, sortMimeTypes } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellEditType, CellKind, DisplayOrderKey, ICellEditOperation, INotebookDecorationRenderOptions, INotebookKernelInfo2, INotebookKernelProvider, INotebookMarkdownRendererInfo, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, notebookDocumentFilterMatch, NotebookEditorPriority, NOTEBOOK_DISPLAY_ORDER, RENDERER_NOT_AVAILABLE, sortMimeTypes } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookMarkdownRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookMarkdownRenderer';
import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer';
import { NotebookEditorDescriptor, NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
@ -235,11 +236,13 @@ class ModelData implements IDisposable {
this._modelEventListeners.dispose();
}
}
export class NotebookService extends Disposable implements INotebookService, ICustomEditorViewTypesHandler {
declare readonly _serviceBrand: undefined;
private readonly _notebookProviders = new Map<string, { controller: IMainNotebookController, extensionData: NotebookExtensionDescription; }>();
notebookProviderInfoStore: NotebookProviderInfoStore;
notebookRenderersInfoStore: NotebookOutputRendererInfoStore = new NotebookOutputRendererInfoStore();
private readonly markdownRenderersInfos = new Set<INotebookMarkdownRendererInfo>();
notebookKernelProviderInfoStore: NotebookKernelProviderInfoStore = new NotebookKernelProviderInfoStore();
private readonly _models = new ResourceMap<ModelData>();
private _onDidChangeActiveEditor = new Emitter<string | null>();
@ -320,6 +323,32 @@ export class NotebookService extends Disposable implements INotebookService, ICu
}
});
notebookMarkdownRendererExtensionPoint.setHandler((renderers) => {
this.markdownRenderersInfos.clear();
for (const extension of renderers) {
for (const notebookContribution of extension.value) {
if (!notebookContribution.entrypoint) { // avoid crashing
console.error(`Cannot register renderer for ${extension.description.identifier.value} since it did not have an entrypoint. This is now required: https://github.com/microsoft/vscode/issues/102644`);
continue;
}
const id = notebookContribution.id ?? notebookContribution.viewType;
if (!id) {
console.error(`Notebook renderer from ${extension.description.identifier.value} is missing an 'id'`);
continue;
}
this.markdownRenderersInfos.add(new NotebookMarkdownRendererInfo({
id,
extension: extension.description,
entrypoint: notebookContribution.entrypoint,
displayName: 'todo',
}));
}
}
});
this._editorService.registerCustomEditorViewTypesHandler('Notebook', this);
const updateOrder = () => {
@ -740,6 +769,10 @@ export class NotebookService extends Disposable implements INotebookService, ICu
return this.notebookRenderersInfoStore.get(id);
}
getMarkdownRendererInfo(): INotebookMarkdownRendererInfo[] {
return Array.from(this.markdownRenderersInfos);
}
async resolveNotebook(viewType: string, uri: URI, forceReload: boolean, backupId?: string): Promise<NotebookTextModel> {
if (!await this.canResolve(viewType)) {

View file

@ -17,7 +17,7 @@ import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IFileService } from 'vs/platform/files/common/files';
import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { ICellOutputViewModel, ICommonCellInfo, ICommonNotebookEditor, IDisplayOutputLayoutUpdateRequest, IGenericCellViewModel, IInsetRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellEditState, ICellOutputViewModel, ICommonCellInfo, ICommonNotebookEditor, IDisplayOutputLayoutUpdateRequest, IDisplayOutputViewModel, IGenericCellViewModel, IInsetRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { preloadsScriptStr } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads';
import { transformWebviewThemeVars } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewThemeMapping';
import { INotebookRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon';
@ -37,6 +37,7 @@ export interface IDimensionMessage {
id: string;
init: boolean;
data: DOM.Dimension;
isOutput: boolean;
}
export interface IMouseEnterMessage {
@ -79,6 +80,38 @@ export interface IClickedDataUrlMessage {
downloadName?: string;
}
export interface IToggleMarkdownPreviewMessage {
__vscode_notebook_message: boolean;
type: 'toggleMarkdownPreview';
cellId: string;
}
export interface ICellDragStartMessage {
__vscode_notebook_message: boolean;
type: 'cell-drag-start';
cellId: string;
position: { clientX: number, clientY: number };
}
export interface ICellDragMessage {
__vscode_notebook_message: boolean;
type: 'cell-drag';
cellId: string;
position: { clientX: number, clientY: number };
}
export interface ICellDragEndMessage {
readonly __vscode_notebook_message: boolean;
readonly type: 'cell-drag-end';
readonly cellId: string;
readonly ctrlKey: boolean
readonly altKey: boolean;
readonly position: {
readonly clientX: number;
readonly clientY: number;
};
}
export interface IClearMessage {
type: 'clear';
}
@ -128,6 +161,11 @@ export interface IViewScrollTopRequestMessage {
version: number;
}
export interface IViewScrollMarkdownRequestMessage {
type: 'view-scroll-markdown';
cells: { id: string; top: number }[];
}
export interface IScrollRequestMessage {
type: 'scroll';
id: string;
@ -187,6 +225,35 @@ export interface ICustomRendererMessage {
message: unknown;
}
export interface ICreateMarkdownMessage {
type: 'createMarkdownPreview',
id: string;
content: string;
top: number;
}
export interface IRemoveMarkdownMessage {
type: 'removeMarkdownPreview',
id: string;
}
export interface IHideMarkdownMessage {
type: 'hideMarkdownPreview',
id: string;
}
export interface IShowMarkdownMessage {
type: 'showMarkdownPreview',
id: string;
content: string;
top: number;
}
export interface IInitializeMarkdownMessage {
type: 'initializeMarkdownPreview';
cells: Array<{ cellId: string, content: string }>;
}
export type FromWebviewMessage =
| WebviewIntialized
| IDimensionMessage
@ -196,8 +263,12 @@ export type FromWebviewMessage =
| IScrollAckMessage
| IBlurOutputMessage
| ICustomRendererMessage
| IClickedDataUrlMessage;
| IClickedDataUrlMessage
| IToggleMarkdownPreviewMessage
| ICellDragStartMessage
| ICellDragMessage
| ICellDragEndMessage
;
export type ToWebviewMessage =
| IClearMessage
| IFocusOutputMessage
@ -209,7 +280,13 @@ export type ToWebviewMessage =
| IShowOutputMessage
| IUpdatePreloadResourceMessage
| IUpdateDecorationsMessage
| ICustomRendererMessage;
| ICustomRendererMessage
| ICreateMarkdownMessage
| IRemoveMarkdownMessage
| IShowMarkdownMessage
| IHideMarkdownMessage
| IInitializeMarkdownMessage
| IViewScrollMarkdownRequestMessage;
export type AnyMessage = FromWebviewMessage | ToWebviewMessage;
@ -237,9 +314,10 @@ let version = 0;
export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
element: HTMLElement;
webview: WebviewElement | undefined = undefined;
insetMapping: Map<ICellOutputViewModel, ICachedInset<T>> = new Map();
hiddenInsetMapping: Set<ICellOutputViewModel> = new Set();
reversedInsetMapping: Map<string, ICellOutputViewModel> = new Map();
insetMapping: Map<IDisplayOutputViewModel, ICachedInset<T>> = new Map();
markdownPreviewMapping: Set<string> = new Set();
hiddenInsetMapping: Set<IDisplayOutputViewModel> = new Set();
reversedInsetMapping: Map<string, IDisplayOutputViewModel> = new Map();
localResourceRootsCache: URI[] | undefined = undefined;
rendererRootsCache: URI[] = [];
kernelRootsCache: URI[] = [];
@ -273,20 +351,173 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
this.element.style.height = '1400px';
this.element.style.position = 'absolute';
}
generateContent(coreDependencies: string, baseUrl: string) {
private generateContent(coreDependencies: string, baseUrl: string) {
const markdownRenderersSrc = this.getMarkdownRendererScripts();
return html`
<html lang="en">
<head>
<meta charset="UTF-8">
<base href="${baseUrl}/"/>
<style>
#container > div > div {
#container > div > div.output {
width: 100%;
padding: ${this.options.outputNodePadding}px ${this.options.outputNodePadding}px ${this.options.outputNodePadding}px ${this.options.outputNodeLeftPadding}px;
box-sizing: border-box;
background-color: var(--vscode-notebook-outputContainerBackgroundColor);
}
#container > div > div.preview {
width: 100%;
box-sizing: border-box;
white-space: nowrap;
overflow: hidden;
user-select: text;
-webkit-user-select: text;
-ms-user-select: text;
white-space: initial;
padding-left: 0px !important;
}
/* markdown */
#container > div > div.preview img {
max-width: 100%;
max-height: 100%;
}
#container > div > div.preview a {
text-decoration: none;
}
#container > div > div.preview a:hover {
text-decoration: underline;
}
#container > div > div.preview a:focus,
#container > div > div.preview input:focus,
#container > div > div.preview select:focus,
#container > div > div.preview textarea:focus {
outline: 1px solid -webkit-focus-ring-color;
outline-offset: -1px;
}
#container > div > div.preview hr {
border: 0;
height: 2px;
border-bottom: 2px solid;
}
#container > div > div.preview h1 {
padding-bottom: 0.3em;
line-height: 1.2;
border-bottom-width: 1px;
border-bottom-style: solid;
border-color: rgba(255, 255, 255, 0.18);
}
#container > div > div.preview h1 {
border-color: rgba(0, 0, 0, 0.18);
}
#container > div > div.preview h1,
#container > div > div.preview h2,
#container > div > div.preview h3 {
font-weight: normal;
}
#container > div > div.preview div {
width: 100%;
}
/* Adjust margin of first item in markdown cell */
#container > div > div.preview *:first-child {
margin-top: 0px;
}
/* h1 tags don't need top margin */
#container > div > div.preview h1:first-child {
margin-top: 0;
}
/* Removes bottom margin when only one item exists in markdown cell */
#container > div > div.preview *:only-child,
#container > div > div.preview *:last-child {
margin-bottom: 0;
padding-bottom: 0;
}
/* makes all markdown cells consistent */
#container > div > div.preview div {
min-height: 24px;
}
#container > div > div.preview table {
border-collapse: collapse;
border-spacing: 0;
}
#container > div > div.preview table th,
#container > div > div.preview table td {
border: 1px solid;
}
#container > div > div.preview table > thead > tr > th {
text-align: left;
border-bottom: 1px solid;
}
#container > div > div.preview table > thead > tr > th,
#container > div > div.preview table > thead > tr > td,
#container > div > div.preview table > tbody > tr > th,
#container > div > div.preview table > tbody > tr > td {
padding: 5px 10px;
}
#container > div > div.preview table > tbody > tr + tr > td {
border-top: 1px solid;
}
#container > div > div.preview blockquote {
margin: 0 7px 0 5px;
padding: 0 16px 0 10px;
border-left-width: 5px;
border-left-style: solid;
}
#container > div > div.preview code,
#container > div > div.preview .code {
font-family: var(--monaco-monospace-font);
font-size: 1em;
line-height: 1.357em;
}
#container > div > div.preview .code {
white-space: pre-wrap;
}
#container > div > div.preview .latex-block {
display: block;
}
#container > div > div.preview .latex {
vertical-align: middle;
display: inline-block;
}
#container > div > div.preview .latex img,
#container > div > div.preview .latex-block img {
filter: brightness(0) invert(0)
}
#container > div > div.preview.dragging {
background-color: var(--vscode-editor-background);
}
.monaco-workbench.vs-dark .notebookOverlay .cell.markdown .latex img,
.monaco-workbench.vs-dark .notebookOverlay .cell.markdown .latex-block img {
filter: brightness(0) invert(1)
}
#container > div.nb-symbolHighlight > div {
background-color: var(--vscode-notebook-symbolHighlightBackground);
}
@ -350,10 +581,29 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
${coreDependencies}
<div id='container' class="widgetarea" style="position: absolute;width:100%;top: 0px"></div>
<script>${preloadsScriptStr(this.options.outputNodePadding, this.options.outputNodeLeftPadding)}</script>
${markdownRenderersSrc}
</body>
</html>`;
}
private getMarkdownRendererScripts() {
const markdownRenderers = this.notebookService.getMarkdownRendererInfo();
return markdownRenderers
.sort((a, b) => {
// prefer built-in extension
if (a.extensionIsBuiltin) {
return b.extensionIsBuiltin ? 0 : -1;
}
return b.extensionIsBuiltin ? 1 : -1;
})
.map(renderer => {
return asWebviewUri(this.environmentService, this.id, renderer.entrypoint);
})
.map(src => `<script src="${src}"></script>`)
.join('\n');
}
postRendererMessage(rendererId: string, message: any) {
this._sendMessageToWebview({
__vscode_notebook_message: true,
@ -476,13 +726,18 @@ var requirejs = (function() {
if (data.__vscode_notebook_message) {
if (data.type === 'dimension') {
const height = data.data.height;
const outputHeight = height;
if (data.isOutput) {
const height = data.data.height;
const outputHeight = height;
const resolvedResult = this.resolveOutputId(data.id);
if (resolvedResult) {
const { cellInfo, output } = resolvedResult;
this.notebookEditor.updateOutputHeight(cellInfo, output, outputHeight, !!data.init);
const resolvedResult = this.resolveOutputId(data.id);
if (resolvedResult) {
const { cellInfo, output } = resolvedResult;
this.notebookEditor.updateOutputHeight(cellInfo, output, outputHeight, !!data.init);
}
} else {
const cellId = data.id.substr(0, data.id.length - '_preview'.length);
this.notebookEditor.updateMarkdownCellHeight(cellId, data.data.height, !!data.init);
}
} else if (data.type === 'mouseenter') {
const resolvedResult = this.resolveOutputId(data.id);
@ -528,6 +783,18 @@ var requirejs = (function() {
this._onDidClickDataLink(data);
} else if (data.type === 'customRendererMessage') {
this._onMessage.fire({ message: data.message, forRenderer: data.rendererId });
} else if (data.type === 'toggleMarkdownPreview') {
this.notebookEditor.setMarkdownCellEditState(data.cellId, CellEditState.Editing);
} else if (data.type === 'cell-drag-start') {
this.notebookEditor.markdownCellDragStart(data.cellId, data.position);
} else if (data.type === 'cell-drag') {
this.notebookEditor.markdownCellDrag(data.cellId, data.position);
} else if (data.type === 'cell-drag-end') {
this.notebookEditor.markdownCellDragEnd(data.cellId, {
clientY: data.position.clientY,
ctrlKey: data.ctrlKey,
altKey: data.altKey,
});
}
return;
}
@ -576,7 +843,12 @@ var requirejs = (function() {
const workspaceFolders = this.contextService.getWorkspace().folders.map(x => x.uri);
this.localResourceRootsCache = [...this.notebookService.getNotebookProviderResourceRoots(), ...workspaceFolders, rootPath];
this.localResourceRootsCache = [
...this.notebookService.getNotebookProviderResourceRoots(),
...this.notebookService.getMarkdownRendererInfo().map(x => dirname(x.entrypoint)),
...workspaceFolders,
rootPath,
];
const webview = webviewService.createWebviewElement(this.id, {
purpose: WebviewContentPurpose.NotebookRenderer,
@ -628,6 +900,13 @@ var requirejs = (function() {
return true;
}
updateMarkdownScrollTop(items: { id: string, top: number }[]) {
this._sendMessageToWebview({
type: 'view-scroll-markdown',
cells: items
});
}
updateViewScrollTop(top: number, forceDisplay: boolean, items: IDisplayOutputLayoutUpdateRequest[]) {
if (this._disposed) {
return;
@ -656,6 +935,83 @@ var requirejs = (function() {
});
}
async createMarkdownPreview(cellId: string, content: string, cellTop: number) {
if (this._disposed) {
return;
}
const initialTop = cellTop;
this.markdownPreviewMapping.add(cellId);
this._sendMessageToWebview({
type: 'createMarkdownPreview',
id: cellId,
content: content,
top: initialTop,
});
}
async showMarkdownPreview(cellId: string, content: string, cellTop: number) {
if (this._disposed) {
return;
}
this._sendMessageToWebview({
type: 'showMarkdownPreview',
id: cellId,
content: content,
top: cellTop
});
}
async hideMarkdownPreview(cellId: string,) {
if (this._disposed) {
return;
}
this._sendMessageToWebview({
type: 'hideMarkdownPreview',
id: cellId
});
}
async removeMarkdownPreview(cellId: string,) {
if (this._disposed) {
return;
}
this.markdownPreviewMapping.delete(cellId);
this._sendMessageToWebview({
type: 'removeMarkdownPreview',
id: cellId
});
}
async initializeMarkdown(cells: Array<{ cellId: string, content: string }>) {
await this._loaded;
// TODO: use proper handler
const p = new Promise<void>(resolve => {
this.webview?.onMessage(e => {
if (e.type === 'initializedMarkdownPreview') {
resolve();
}
});
});
for (const cell of cells) {
this.markdownPreviewMapping.add(cell.cellId);
}
this._sendMessageToWebview({
type: 'initializeMarkdownPreview',
cells: cells,
});
await p;
}
async createInset(cellInfo: T, content: IInsetRenderOutput, cellTop: number, offset: number) {
if (this._disposed) {
return;

View file

@ -149,7 +149,7 @@ export class CellDragAndDropController extends Disposable {
return;
}
const dropDirection = this.getDropInsertDirection(event);
const dropDirection = this.getDropInsertDirection(event.dragPosRatio);
const insertionIndicatorAbsolutePos = dropDirection === 'above' ? event.cellTop : event.cellTop + event.cellHeight;
const insertionIndicatorTop = insertionIndicatorAbsolutePos - this.list.scrollTop + BOTTOM_CELL_TOOLBAR_GAP / 2;
if (insertionIndicatorTop >= 0) {
@ -160,8 +160,8 @@ export class CellDragAndDropController extends Disposable {
}
}
private getDropInsertDirection(event: CellDragEvent): 'above' | 'below' {
return event.dragPosRatio < 0.5 ? 'above' : 'below';
private getDropInsertDirection(dragPosRatio: number): 'above' | 'below' {
return dragPosRatio < 0.5 ? 'above' : 'below';
}
private onCellDrop(event: CellDragEvent): void {
@ -189,7 +189,7 @@ export class CellDragAndDropController extends Disposable {
const isCopy = (event.browserEvent.ctrlKey && !platform.isMacintosh) || (event.browserEvent.altKey && platform.isMacintosh);
const dropDirection = this.getDropInsertDirection(event);
const dropDirection = this.getDropInsertDirection(event.dragPosRatio);
const insertionIndicatorAbsolutePos = dropDirection === 'above' ? event.cellTop : event.cellTop + event.cellHeight;
const insertionIndicatorTop = insertionIndicatorAbsolutePos - this.list.scrollTop + BOTTOM_CELL_TOOLBAR_GAP / 2;
const editorHeight = this.notebookEditor.getDomNode().getBoundingClientRect().height;
@ -275,4 +275,65 @@ export class CellDragAndDropController extends Disposable {
this.notebookEditor.textModel!.pushStackElement('Copy Cells', undefined, undefined);
}
public startExplicitDrag(cell: ICellViewModel, position: { clientY: number }) {
this.currentDraggedCell = cell;
this.setInsertIndicatorVisibility(true);
}
public explicitDrag(cell: ICellViewModel, position: { clientY: number }) {
const target = this.list.elementAt(position.clientY);
if (target && target !== cell) {
const cellTop = this.list.getAbsoluteTopOfElement(target);
const cellHeight = this.list.elementHeight(target);
const dragOffset = this.list.scrollTop + position.clientY - cellTop;
const dragPosInElement = dragOffset - cellTop;
const dragPosRatio = dragPosInElement / cellHeight;
const dropDirection = this.getDropInsertDirection(dragPosRatio);
const insertionIndicatorAbsolutePos = dropDirection === 'above' ? cellTop : cellTop + cellHeight;
const insertionIndicatorTop = insertionIndicatorAbsolutePos - this.list.scrollTop + BOTTOM_CELL_TOOLBAR_GAP / 2;
if (insertionIndicatorTop >= 0) {
this.listInsertionIndicator.style.top = `${insertionIndicatorTop}px`;
}
}
}
public endExplicitDrag(cell: ICellViewModel, ctx: { clientY: number, ctrlKey: boolean, altKey: boolean }) {
this.currentDraggedCell = undefined;
this.setInsertIndicatorVisibility(false);
const target = this.list.elementAt(ctx.clientY);
if (!target || target === cell) {
return;
}
const cellTop = this.list.getAbsoluteTopOfElement(target);
const cellHeight = this.list.elementHeight(target);
const dragOffset = this.list.scrollTop + ctx.clientY - cellTop;
const dragPosInElement = dragOffset - cellTop;
const dragPosRatio = dragPosInElement / cellHeight;
const dropDirection = this.getDropInsertDirection(dragPosRatio);
const isCopy = (ctx.ctrlKey && !platform.isMacintosh) || (ctx.altKey && platform.isMacintosh);
if (isCopy) {
this.copyCells([cell], target, dropDirection);
} else {
const viewModel = this.notebookEditor.viewModel!;
let originalToIdx = viewModel.getCellIndex(target);
if (dropDirection === 'below') {
const relativeToIndex = viewModel.getCellIndex(target);
const newIdx = viewModel.getNextVisibleCellIndex(relativeToIndex);
originalToIdx = newIdx;
}
const draggedCellRange = [this.notebookEditor.viewModel!.getCellIndex(cell), 1];
this.notebookEditor.moveCellsToIdx(draggedCellRange[0], draggedCellRange[1], originalToIdx);
}
}
}

View file

@ -414,6 +414,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR
const bottomCellContainer = DOM.append(container, $('.cell-bottom-toolbar-container'));
const betweenCellToolbar = disposables.add(this.createBetweenCellToolbar(bottomCellContainer, disposables, contextKeyService));
const focusIndicatorBottom = DOM.append(container, $('.cell-focus-indicator.cell-focus-indicator-bottom'));
const statusBar = disposables.add(this.instantiationService.createInstance(CellEditorStatusBar, editorPart));
DOM.hide(statusBar.durationContainer);
@ -432,6 +433,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR
editorPart,
editorContainer,
focusIndicatorLeft,
focusIndicatorBottom,
foldingIndicator,
disposables,
elementDisposables: new DisposableStore(),
@ -524,6 +526,11 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR
elementDisposables.add(new CellContextKeyManager(templateData.contextKeyService, this.notebookEditor, this.notebookEditor.viewModel.notebookDocument!, element));
this.updateForLayout(element, templateData);
elementDisposables.add(element.onDidChangeLayout(() => {
this.updateForLayout(element, templateData);
}));
// render toolbar first
this.setupCellToolbarActions(templateData, elementDisposables);
@ -545,6 +552,11 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR
templateData.statusBar.update(toolbarContext);
}
private updateForLayout(element: MarkdownCellViewModel, templateData: MarkdownCellRenderTemplate): void {
// templateData.focusIndicatorLeft.style.height = `${element.layoutInfo.indicatorHeight}px`;
templateData.focusIndicatorBottom.style.top = `${element.layoutInfo.totalHeight - BOTTOM_CELL_TOOLBAR_GAP - CELL_BOTTOM_MARGIN}px`;
}
disposeTemplate(templateData: MarkdownCellRenderTemplate): void {
templateData.disposables.clear();
}

View file

@ -23,16 +23,97 @@ import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/com
import { NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { collapsedIcon, expandedIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons';
import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
interface IMarkdownRenderStrategy extends IDisposable {
update(): void;
}
class WebviewMarkdownRenderer extends Disposable implements IMarkdownRenderStrategy {
constructor(
readonly notebookEditor: IActiveNotebookEditor,
readonly viewCell: MarkdownCellViewModel
) {
super();
}
update(): void {
this.notebookEditor.createMarkdownPreview(this.viewCell);
}
}
class BuiltinMarkdownRenderer extends Disposable implements IMarkdownRenderStrategy {
private localDisposables = new DisposableStore();
constructor(
readonly notebookEditor: IActiveNotebookEditor,
readonly viewCell: MarkdownCellViewModel,
readonly container: HTMLElement,
readonly markdownContainer: HTMLElement,
readonly dataSource: { codeEditor: CodeEditorWidget | null; }
) {
super();
this._register(getResizesObserver(this.markdownContainer, undefined, () => {
if (viewCell.editState === CellEditState.Preview) {
this.viewCell.renderedMarkdownHeight = container.clientHeight;
}
})).startObserving();
}
update(): void {
const markdownRenderer = this.viewCell.getMarkdownRenderer();
const renderedHTML = this.viewCell.getHTML();
if (renderedHTML) {
this.markdownContainer.appendChild(renderedHTML);
}
if (this.dataSource.codeEditor) {
// switch from editing mode
this.viewCell.renderedMarkdownHeight = this.container.clientHeight;
this.relayoutCell();
} else {
// first time, readonly mode
this.localDisposables.add(markdownRenderer.onDidRenderAsync(() => {
this.viewCell.renderedMarkdownHeight = this.container.clientHeight;
this.relayoutCell();
}));
this.localDisposables.add(this.viewCell.textBuffer.onDidChangeContent(() => {
this.markdownContainer.innerText = '';
this.viewCell.clearHTML();
const renderedHTML = this.viewCell.getHTML();
if (renderedHTML) {
this.markdownContainer.appendChild(renderedHTML);
}
}));
this.viewCell.renderedMarkdownHeight = this.container.clientHeight;
this.relayoutCell();
}
}
relayoutCell() {
this.notebookEditor.layoutNotebookCell(this.viewCell, this.viewCell.layoutInfo.totalHeight);
}
}
export class StatefulMarkdownCell extends Disposable {
private editor: CodeEditorWidget | null = null;
get codeEditor() {
return this.editor;
}
private markdownContainer: HTMLElement;
private editorPart: HTMLElement;
private localDisposables = new DisposableStore();
private foldingState: CellFoldingState;
private _activeCellRunPlaceholder: IDisposable | null = null;
private _useRenderer: boolean = false;
private _renderStrategy: IMarkdownRenderStrategy;
constructor(
private readonly notebookEditor: IActiveNotebookEditor,
@ -43,11 +124,21 @@ export class StatefulMarkdownCell extends Disposable {
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@INotebookCellStatusBarService readonly notebookCellStatusBarService: INotebookCellStatusBarService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IConfigurationService private readonly configurationSerivce: IConfigurationService
) {
super();
this.markdownContainer = templateData.cellContainer;
this.editorPart = templateData.editorPart;
this._useRenderer = !!(this.configurationSerivce.getValue<string>('notebook.experimental.useMarkdownRenderer'));
if (this._useRenderer) {
this._renderStrategy = new WebviewMarkdownRenderer(this.notebookEditor, this.viewCell);
} else {
this._renderStrategy = new BuiltinMarkdownRenderer(this.notebookEditor, this.viewCell, this.templateData.container, this.markdownContainer, this);
}
this._register(this._renderStrategy);
this._register(this.localDisposables);
this._register(toDisposable(() => renderedEditors.delete(this.viewCell)));
@ -64,12 +155,6 @@ export class StatefulMarkdownCell extends Disposable {
this.viewUpdate();
}));
this._register(getResizesObserver(this.markdownContainer, undefined, () => {
if (viewCell.editState === CellEditState.Preview) {
this.viewCell.renderedMarkdownHeight = templateData.container.clientHeight;
}
})).startObserving();
const updateForFocusMode = () => {
if (viewCell.focusMode === CellFocusMode.Editor) {
this.focusEditorIfNeeded();
@ -286,35 +371,8 @@ export class StatefulMarkdownCell extends Disposable {
this.markdownContainer.innerText = '';
this.viewCell.clearHTML();
const markdownRenderer = this.viewCell.getMarkdownRenderer();
const renderedHTML = this.viewCell.getHTML();
if (renderedHTML) {
this.markdownContainer.appendChild(renderedHTML);
}
if (this.editor) {
// switch from editing mode
this.viewCell.renderedMarkdownHeight = this.templateData.container.clientHeight;
this.relayoutCell();
} else {
// first time, readonly mode
this.localDisposables.add(markdownRenderer.onDidRenderAsync(() => {
this.viewCell.renderedMarkdownHeight = this.templateData.container.clientHeight;
this.relayoutCell();
}));
this.localDisposables.add(this.viewCell.textBuffer.onDidChangeContent(() => {
this.markdownContainer.innerText = '';
this.viewCell.clearHTML();
const renderedHTML = this.viewCell.getHTML();
if (renderedHTML) {
this.markdownContainer.appendChild(renderedHTML);
}
}));
this.viewCell.renderedMarkdownHeight = this.templateData.container.clientHeight;
this.relayoutCell();
}
this._renderStrategy.update();
}
private focusEditorIfNeeded() {
@ -342,7 +400,7 @@ export class StatefulMarkdownCell extends Disposable {
// this.relayoutCell();
}
private relayoutCell(): void {
relayoutCell(): void {
this.notebookEditor.layoutNotebookCell(this.viewCell, this.viewCell.layoutInfo.totalHeight);
}
@ -420,6 +478,7 @@ export class StatefulMarkdownCell extends Disposable {
}
dispose() {
this.notebookEditor.removeMarkdownPreview(this.viewCell);
this.viewCell.detachTextEditor();
super.dispose();
}

View file

@ -5,7 +5,7 @@
import type { Event } from 'vs/base/common/event';
import type { IDisposable } from 'vs/base/common/lifecycle';
import { ToWebviewMessage } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView';
import { ICellDragEndMessage, ICellDragMessage, ICellDragStartMessage, ToWebviewMessage } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView';
import { RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
// !! IMPORTANT !! everything must be in-line within the webviewPreloads
@ -136,7 +136,7 @@ function webviewPreloads() {
const outputObservers = new Map<string, ResizeObserver>();
const resizeObserve = (container: Element, id: string) => {
const resizeObserve = (container: Element, id: string, output: boolean) => {
const resizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
if (!document.body.contains(entry.target)) {
@ -152,7 +152,8 @@ function webviewPreloads() {
id: id,
data: {
height: entry.contentRect.height + __outputNodePadding__ * 2
}
},
isOutput: output
});
} else {
entry.target.style.padding = `0px`;
@ -162,7 +163,8 @@ function webviewPreloads() {
id: id,
data: {
height: entry.contentRect.height
}
},
isOutput: output
});
}
}
@ -320,6 +322,10 @@ function webviewPreloads() {
mimeType?: string;
element: HTMLElement;
}
interface ICreateMarkdownInfo {
readonly content: string;
readonly element: HTMLElement;
}
interface IDestroyCellInfo {
outputId: string;
@ -327,6 +333,7 @@ function webviewPreloads() {
const onWillDestroyOutput = createEmitter<[string | undefined /* namespace */, IDestroyCellInfo | undefined /* cell uri */]>();
const onDidCreateOutput = createEmitter<[string | undefined /* namespace */, ICreateCellInfo]>();
const onDidCreateMarkdown = createEmitter<[string | undefined /* namespace */, ICreateMarkdownInfo]>();
const onDidReceiveMessage = createEmitter<[string, unknown]>();
const matchesNs = (namespace: string, query: string | undefined) => namespace === '*' || query === namespace || query === 'undefined';
@ -355,6 +362,7 @@ function webviewPreloads() {
onDidReceiveMessage: mapEmitter(onDidReceiveMessage, ([ns, data]) => ns === namespace ? data : dontEmit),
onWillDestroyOutput: mapEmitter(onWillDestroyOutput, ([ns, data]) => matchesNs(namespace, ns) ? data : dontEmit),
onDidCreateOutput: mapEmitter(onDidCreateOutput, ([ns, data]) => matchesNs(namespace, ns) ? data : dontEmit),
onDidCreateMarkdown: mapEmitter(onDidCreateMarkdown, ([ns, data]) => data),
};
};
@ -405,6 +413,51 @@ function webviewPreloads() {
const event = rawEvent as ({ data: ToWebviewMessage; });
switch (event.data.type) {
case 'initializeMarkdownPreview':
for (const cell of event.data.cells) {
createMarkdownPreview(cell.cellId, cell.content, -10000);
}
vscode.postMessage({
__vscode_notebook_message: true,
type: 'initializedMarkdownPreview',
});
break;
case 'createMarkdownPreview':
createMarkdownPreview(event.data.id, event.data.content, event.data.top);
break;
case 'showMarkdownPreview':
{
const data = event.data;
let cellContainer = document.getElementById(data.id);
if (cellContainer) {
cellContainer.style.display = 'block';
}
const previewNode = document.getElementById(`${data.id}_container`);
if (previewNode) {
previewNode.style.top = `${data.top}px`;
}
updateMarkdownPreview(data.id, data.content);
}
break;
case 'hideMarkdownPreview':
{
const data = event.data;
let cellContainer = document.getElementById(data.id);
if (cellContainer) {
cellContainer.style.display = 'none';
}
}
break;
case 'removeMarkdownPreview':
{
const data = event.data;
let cellContainer = document.getElementById(data.id);
if (cellContainer) {
cellContainer?.parentElement?.removeChild(cellContainer);
}
}
break;
case 'html':
enqueueOutputAction(event.data, async data => {
const preloadResults = await Promise.all(data.requiredPreloads.map(p => preloadPromises.get(p.uri)));
@ -431,6 +484,7 @@ function webviewPreloads() {
}
const outputNode = document.createElement('div');
outputNode.classList.add('output');
outputNode.style.position = 'absolute';
outputNode.style.top = data.top + 'px';
outputNode.style.left = data.left + 'px';
@ -468,7 +522,7 @@ function webviewPreloads() {
cellOutputContainer.appendChild(outputNode);
}
resizeObserve(outputNode, outputId);
resizeObserve(outputNode, outputId, true);
vscode.postMessage({
__vscode_notebook_message: true,
@ -498,6 +552,21 @@ function webviewPreloads() {
}
}
}
break;
}
case 'view-scroll-markdown':
{
// const date = new Date();
// console.log('----- will scroll ---- ', date.getMinutes() + ':' + date.getSeconds() + ':' + date.getMilliseconds());
event.data.cells.map(cell => {
const widget = document.getElementById(`${cell.id}_preview`)!;
if (widget) {
widget.style.top = `${cell.top}px`;
}
});
break;
}
case 'clear':
@ -589,6 +658,127 @@ function webviewPreloads() {
__vscode_notebook_message: true,
type: 'initialized'
});
document.addEventListener('dragover', e => {
// Allow dropping dragged markdown cells
e.preventDefault();
});
const markdownCellDragDataType = 'x-vscode-markdown-cell-drag';
document.addEventListener('drop', e => {
const data = e.dataTransfer?.getData(markdownCellDragDataType);
if (!data) {
return;
}
e.preventDefault();
const { cellId } = JSON.parse(data);
const msg: ICellDragEndMessage = {
__vscode_notebook_message: true,
type: 'cell-drag-end',
cellId: cellId,
ctrlKey: e.ctrlKey,
altKey: e.altKey,
position: { clientX: e.clientX, clientY: e.clientY },
};
vscode.postMessage(msg);
});
function createMarkdownPreview(cellId: string, content: string, top: number) {
let cellContainer = document.getElementById(cellId);
if (!cellContainer) {
const container = document.getElementById('container')!;
const newElement = document.createElement('div');
newElement.id = `${cellId}`;
container.appendChild(newElement);
cellContainer = newElement;
const previewContainerNode = document.createElement('div');
previewContainerNode.style.position = 'absolute';
previewContainerNode.style.top = top + 'px';
previewContainerNode.id = `${cellId}_preview`;
previewContainerNode.classList.add('preview');
previewContainerNode.addEventListener('dblclick', () => {
vscode.postMessage({
__vscode_notebook_message: true,
type: 'toggleMarkdownPreview',
cellId,
});
});
previewContainerNode.setAttribute('draggable', 'true');
previewContainerNode.addEventListener('dragstart', e => {
if (!e.dataTransfer) {
return;
}
e.dataTransfer.setData(markdownCellDragDataType, JSON.stringify({ cellId }));
(e.target as HTMLElement).classList.add('dragging');
const msg: ICellDragStartMessage = {
__vscode_notebook_message: true,
type: 'cell-drag-start',
cellId: cellId,
position: { clientX: e.clientX, clientY: e.clientY },
};
vscode.postMessage(msg);
});
previewContainerNode.addEventListener('drag', e => {
const msg: ICellDragMessage = {
__vscode_notebook_message: true,
type: 'cell-drag',
cellId: cellId,
position: { clientX: e.clientX, clientY: e.clientY },
};
vscode.postMessage(msg);
});
previewContainerNode.addEventListener('dragend', e => {
(e.target as HTMLElement).classList.remove('dragging');
});
cellContainer.appendChild(previewContainerNode);
const previewNode = document.createElement('div');
previewContainerNode.appendChild(previewNode);
// TODO: handle namespace
onDidCreateMarkdown.fire([undefined /* data.apiNamespace */, {
element: previewNode,
content: content
}]);
resizeObserve(previewContainerNode, `${cellId}_preview`, false);
vscode.postMessage({
__vscode_notebook_message: true,
type: 'dimension',
id: `${cellId}_preview`,
init: true,
data: {
height: previewContainerNode.clientHeight
},
isOutput: false
});
} else {
updateMarkdownPreview(cellId, content);
}
}
function updateMarkdownPreview(cellId: string, content: string) {
const previewNode = document.getElementById(`${cellId}_preview`);
if (previewNode) {
// TODO: handle namespace
onDidCreateMarkdown.fire([undefined /* data.apiNamespace */, {
element: previewNode,
content: content
}]);
}
}
}
export const preloadsScriptStr = (outputNodePadding: number, outputNodeLeftPadding: number) => `(${webviewPreloads})()`.replace(/__outputNodePadding__/g, `${outputNodePadding}`).replace(/__outputNodeLeftPadding__/g, `${outputNodeLeftPadding}`);

View file

@ -133,6 +133,13 @@ export interface INotebookRendererInfo {
matches(mimeType: string): boolean;
}
export interface INotebookMarkdownRendererInfo {
readonly entrypoint: URI;
readonly extensionLocation: URI;
readonly extensionId: ExtensionIdentifier;
readonly extensionIsBuiltin: boolean;
}
export interface NotebookCellOutputMetadata {
/**
* Additional attributes of a cell metadata.

View file

@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { joinPath } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { INotebookMarkdownRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon';
export class NotebookMarkdownRendererInfo implements INotebookMarkdownRendererInfo {
readonly id: string;
readonly entrypoint: URI;
readonly displayName: string;
readonly extensionLocation: URI;
readonly extensionId: ExtensionIdentifier;
readonly extensionIsBuiltin: boolean;
constructor(descriptor: {
readonly id: string;
readonly displayName: string;
readonly entrypoint: string;
readonly extension: IExtensionDescription;
}) {
this.id = descriptor.id;
this.extensionId = descriptor.extension.identifier;
this.extensionLocation = descriptor.extension.extensionLocation;
this.entrypoint = joinPath(this.extensionLocation, descriptor.entrypoint);
this.displayName = descriptor.displayName;
this.extensionIsBuiltin = descriptor.extension.isBuiltin;
}
}

View file

@ -10,7 +10,7 @@ import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.pr
import { Event } from 'vs/base/common/event';
import {
INotebookTextModel, INotebookRendererInfo,
IEditor, INotebookKernelProvider, INotebookKernelInfo2, TransientMetadata, NotebookDataDto, TransientOptions, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter, IOrderedMimeType, IOutputDto
IEditor, INotebookKernelProvider, INotebookKernelInfo2, TransientMetadata, NotebookDataDto, TransientOptions, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter, IOrderedMimeType, IOutputDto, INotebookMarkdownRendererInfo
} from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { CancellationToken } from 'vs/base/common/cancellation';
@ -56,6 +56,7 @@ export interface INotebookService {
getContributedNotebookKernelProviders(): Promise<INotebookKernelProvider[]>;
getContributedNotebookOutputRenderers(id: string): NotebookOutputRendererInfo | undefined;
getRendererInfo(id: string): INotebookRendererInfo | undefined;
getMarkdownRendererInfo(): INotebookMarkdownRendererInfo[];
resolveNotebook(viewType: string, uri: URI, forceReload: boolean, backupId?: string): Promise<NotebookTextModel>;
getNotebookTextModel(uri: URI): NotebookTextModel | undefined;

View file

@ -12,7 +12,7 @@ import { BareFontInfo } from 'vs/editor/common/config/fontInfo';
import { Range } from 'vs/editor/common/core/range';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { EditorModel } from 'vs/workbench/common/editor';
import { ICellViewModel, INotebookEditor, INotebookEditorContribution, INotebookEditorMouseEvent, NotebookLayoutInfo, INotebookDeltaDecoration, INotebookEditorCreationOptions, NotebookEditorOptions, ICellOutputViewModel, IInsetRenderOutput, ICommonCellInfo, IGenericCellViewModel, INotebookCellOutputLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { ICellViewModel, INotebookEditor, INotebookEditorContribution, INotebookEditorMouseEvent, NotebookLayoutInfo, INotebookDeltaDecoration, INotebookEditorCreationOptions, NotebookEditorOptions, ICellOutputViewModel, IInsetRenderOutput, ICommonCellInfo, IGenericCellViewModel, INotebookCellOutputLayoutInfo, CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer';
import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher';
import { CellViewModel, IModelDecorationsChangeAccessor, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
@ -87,6 +87,18 @@ export class TestNotebookEditor implements INotebookEditor {
updateOutputHeight(cellInfo: ICommonCellInfo, output: ICellOutputViewModel, height: number, isInit: boolean): void {
throw new Error('Method not implemented.');
}
setMarkdownCellEditState(cellId: string, editState: CellEditState): void {
throw new Error('Method not implemented.');
}
markdownCellDragStart(cellId: string, position: { clientY: number }): void {
throw new Error('Method not implemented.');
}
markdownCellDrag(cellId: string, position: { clientY: number }): void {
throw new Error('Method not implemented.');
}
markdownCellDragEnd(cellId: string, position: { clientY: number }): void {
throw new Error('Method not implemented.');
}
async beginComputeContributedKernels(): Promise<INotebookKernelInfo2[]> {
return [];
}
@ -303,6 +315,18 @@ export class TestNotebookEditor implements INotebookEditor {
createInset(cell: CellViewModel, output: IInsetRenderOutput, offset: number): Promise<void> {
return Promise.resolve();
}
createMarkdownPreview(cell: ICellViewModel): Promise<void> {
return Promise.resolve();
}
hideMarkdownPreview(cell: ICellViewModel): Promise<void> {
return Promise.resolve();
}
removeMarkdownPreview(cell: ICellViewModel): Promise<void> {
return Promise.resolve();
}
updateMarkdownCellHeight(cellId: string, height: number, isInit: boolean): void {
// noop
}
removeInset(output: ICellOutputViewModel): void {
// throw new Error('Method not implemented.');
}

View file

@ -254,11 +254,11 @@ export class PreferencesEditor extends EditorPane {
const promise = this.input && this.input.isDirty() ? this.editorService.save({ editor: this.input, groupId: this.group!.id }) : Promise.resolve(true);
promise.then(() => {
if (target === ConfigurationTarget.USER_LOCAL) {
this.preferencesService.switchSettings(ConfigurationTarget.USER_LOCAL, this.preferencesService.userSettingsResource, true);
this.preferencesService.switchSettings(ConfigurationTarget.USER_LOCAL, this.preferencesService.userSettingsResource);
} else if (target === ConfigurationTarget.WORKSPACE) {
this.preferencesService.switchSettings(ConfigurationTarget.WORKSPACE, this.preferencesService.workspaceSettingsResource!, true);
this.preferencesService.switchSettings(ConfigurationTarget.WORKSPACE, this.preferencesService.workspaceSettingsResource!);
} else if (target instanceof URI) {
this.preferencesService.switchSettings(ConfigurationTarget.WORKSPACE_FOLDER, target, true);
this.preferencesService.switchSettings(ConfigurationTarget.WORKSPACE_FOLDER, target);
}
});
}

View file

@ -303,6 +303,10 @@ export class SettingsEditor2 extends EditorPane {
}
private _setOptions(options: SettingsEditorOptions): void {
if (options.focusSearch) {
this.focusSearch();
}
if (options.query) {
this.searchWidget.setValue(options.query);
}

View file

@ -8,6 +8,7 @@ import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import 'vs/css!./media/searchEditor';
import { ICodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
@ -109,6 +110,7 @@ export const openNewSearchEditor =
const activeWorkspaceRootUri = historyService.getLastActiveWorkspaceRoot(Schemas.file);
const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? withNullAsUndefined(workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined;
const activeEditorControl = editorService.activeTextEditorControl;
let activeModel: ICodeEditor | undefined;
let selected = '';
@ -133,7 +135,8 @@ export const openNewSearchEditor =
telemetryService.publicLog2('searchEditor/openNewSearchEditor');
const args: OpenSearchEditorArgs = { query: selected || undefined };
const seedSearchStringFromSelection = _args.location === 'new' || configurationService.getValue<IEditorOptions>('editor').find!.seedSearchStringFromSelection;
const args: OpenSearchEditorArgs = { query: seedSearchStringFromSelection ? selected : undefined };
Object.entries(_args).forEach(([name, value]) => {
if (value !== undefined) {
(args as any)[name as any] = (typeof value === 'string') ? configurationResolverService.resolve(lastActiveWorkspaceRoot, value) : value;

View file

@ -80,11 +80,14 @@ export class HierarchicalByLocationProjection extends Disposable implements ITes
}));
// when test states change, reflect in the tree
this._register(results.onTestChanged(([, { item, state, computedState }]) => {
// todo: optimize this to avoid needing to iterate
this._register(results.onTestChanged(([, { item, state, retired, computedState }]) => {
for (const i of this.items.values()) {
if (i.test.item.extId === item.extId) {
i.ownState = state.state;
i.retired = retired;
refreshComputedState(computedStateAccessor, i, this.addUpdated, computedState);
this.addUpdated(i);
this.updateEmitter.fire();
return;
}
@ -120,6 +123,13 @@ export class HierarchicalByLocationProjection extends Disposable implements ITes
return this.locations.getTestAtPosition(uri, position);
}
/**
* @inheritdoc
*/
public hasTestInDocument(uri: URI) {
return this.locations.hasTestInDocument(uri);
}
/**
* @inheritdoc
*/
@ -222,6 +232,7 @@ export class HierarchicalByLocationProjection extends Disposable implements ITes
const prevState = this.results.getStateByExtId(item.test.item.extId)?.[1];
if (prevState) {
item.ownState = prevState.state.state;
item.retired = prevState.retired;
refreshComputedState(computedStateAccessor, item, this.addUpdated, prevState.computedState);
}
}

View file

@ -41,6 +41,7 @@ export class HierarchicalElement implements ITestTreeElement {
}
public state = TestRunState.Unset;
public retired = false;
public ownState = TestRunState.Unset;
constructor(public readonly test: InternalTestItem, public readonly parentItem: HierarchicalFolder | HierarchicalElement) {
@ -73,6 +74,7 @@ export class HierarchicalFolder implements ITestTreeElement {
return Iterable.concatNested(Iterable.map(this.children, c => c.debuggable));
}
public retired = false;
public state = TestRunState.Unset;
public ownState = TestRunState.Unset;

View file

@ -32,6 +32,11 @@ export interface ITestTreeProjection extends IDisposable {
*/
getTestAtPosition(uri: URI, position: Position): ITestTreeElement | undefined;
/**
* Gets whether any test is defined in the given URI.
*/
hasTestInDocument(uri: URI): boolean;
/**
* Applies pending update to the tree.
*/
@ -82,6 +87,11 @@ export interface ITestTreeElement {
*/
state: TestRunState;
/**
* Whether the node's test result is 'retired' -- from an outdated test run.
*/
readonly retired: boolean;
readonly ownState: TestRunState;
readonly label: string;
readonly parentItem: ITestTreeElement | null;

View file

@ -27,6 +27,10 @@ export const locationsEqual = (a: ModeLocation | undefined, b: ModeLocation | un
export class TestLocationStore<T extends { location?: ModeLocation, depth: number }> {
private readonly itemsByUri = new Map<string, T[]>();
public hasTestInDocument(uri: URI) {
return !!this.itemsByUri.get(uri.toString())?.length;
}
public getTestAtPosition(uri: URI, position: Position) {
const tests = this.itemsByUri.get(uri.toString());
if (!tests) {

View file

@ -15,6 +15,7 @@ export const testingRunIcon = registerIcon('testing-run-icon', Codicon.run, loca
export const testingRunAllIcon = registerIcon('testing-run-all-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.'));
export const testingFilterIcon = registerIcon('testing-filter', Codicon.filter, localize('filterIcon', 'Icon for the \'Filter\' action in the testing view.'));
export const testingShowAsList = registerIcon('testing-show-as-list-icon', Codicon.listTree, localize('testingShowAsList', 'Icon shown when the test explorer is disabled as a tree.'));
export const testingShowAsTree = registerIcon('testing-show-as-list-icon', Codicon.listFlat, localize('testingShowAsTree', 'Icon shown when the test explorer is disabled as a list.'));

View file

@ -41,6 +41,10 @@
margin-right: 0.25em;
}
.test-explorer .computed-state.retired {
opacity: 0.7;
}
.test-explorer .test-explorer-messages {
padding: 0 12px 8px;
}
@ -92,6 +96,27 @@
flex-grow: 1;
}
.testing-filter-action-item .testing-filter-wrapper input {
padding-right: 30px !important;
}
.testing-filter-action-item .monaco-action-bar {
position: absolute;
top: 0;
bottom: 0;
right: 0;
display: flex;
align-items: center;
}
.testing-filter-action-item > .monaco-action-bar .action-item .action-label.codicon.testing-filter-button {
line-height: 20px;
height: 20px;
min-width: 22px;
margin-left: 4px;
justify-content: center;
}
/** -- decorations */
.monaco-editor .testing-run-glyph {

View file

@ -116,8 +116,8 @@ CommandsRegistry.registerCommand({
CommandsRegistry.registerCommand({
id: 'vscode.revealTestInExplorer',
handler: async (accessor: ServicesAccessor, path: string[]) => {
accessor.get(ITestExplorerFilterState).reveal = path;
handler: async (accessor: ServicesAccessor, extId: string) => {
accessor.get(ITestExplorerFilterState).reveal.value = extId;
accessor.get(IViewsService).openView(Testing.ExplorerViewId);
}
});

View file

@ -4,9 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
import { HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { IAction } from 'vs/base/common/actions';
import { Action, IAction, IActionRunner } from 'vs/base/common/actions';
import { Delayer } from 'vs/base/common/async';
import { Event, Emitter } from 'vs/base/common/event';
import { KeyCode } from 'vs/base/common/keyCodes';
@ -14,22 +17,25 @@ import { localize } from 'vs/nls';
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget';
import { ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { inputActiveOptionBorder, inputActiveOptionForeground, inputActiveOptionBackground } from 'vs/platform/theme/common/colorRegistry';
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ViewContainerLocation } from 'vs/workbench/common/views';
import { testingFilterIcon } from 'vs/workbench/contrib/testing/browser/icons';
import { Testing } from 'vs/workbench/contrib/testing/common/constants';
import { ObservableValue } from 'vs/workbench/contrib/testing/common/observableValue';
import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
export interface ITestExplorerFilterState {
_serviceBrand: undefined;
readonly onDidChange: Event<string>;
readonly onDidRequestReveal: Event<string[]>;
value: string;
reveal: string[] | undefined;
readonly text: ObservableValue<string>;
/** Reveal request, the extId of the test to reveal */
readonly reveal: ObservableValue<string | undefined>;
readonly currentDocumentOnly: ObservableValue<boolean>;
readonly onDidRequestInputFocus: Event<void>;
focusInput(): void;
@ -39,37 +45,19 @@ export const ITestExplorerFilterState = createDecorator<ITestExplorerFilterState
export class TestExplorerFilterState implements ITestExplorerFilterState {
declare _serviceBrand: undefined;
private readonly revealRequest = new Emitter<string[]>();
private readonly changeEmitter = new Emitter<string>();
private readonly focusEmitter = new Emitter<void>();
private _value = '';
private _reveal?: string[];
public readonly text = new ObservableValue('');
public readonly currentDocumentOnly = ObservableValue.stored(new StoredValue<boolean>({
key: 'testsByCurrentDocumentOnly',
scope: StorageScope.WORKSPACE,
target: StorageTarget.USER
}, this.storage), false);
public readonly reveal = new ObservableValue<string | undefined>(undefined);
public readonly onDidRequestInputFocus = this.focusEmitter.event;
public readonly onDidRequestReveal = this.revealRequest.event;
public readonly onDidChange = this.changeEmitter.event;
public get reveal() {
return this._reveal;
}
public set reveal(v: string[] | undefined) {
this._reveal = v;
if (v !== undefined) {
this.revealRequest.fire(v);
}
}
public get value() {
return this._value;
}
public set value(v: string) {
if (v !== this._value) {
this._value = v;
this.changeEmitter.fire(v);
}
}
constructor(@IStorageService private readonly storage: IStorageService) { }
public focusInput() {
this.focusEmitter.fire();
@ -84,6 +72,8 @@ export class TestingExplorerFilter extends BaseActionViewItem {
target: StorageTarget.USER
});
private readonly filtersAction = new Action('markersFiltersAction', localize('testing.filters.menu', "More Filters..."), 'testing-filter-button ' + ThemeIcon.asClassName(testingFilterIcon));
constructor(
action: IAction,
@ITestExplorerFilterState private readonly state: ITestExplorerFilterState,
@ -92,6 +82,8 @@ export class TestingExplorerFilter extends BaseActionViewItem {
@IInstantiationService private readonly instantiationService: IInstantiationService,
) {
super(null, action);
this.updateFilterActiveState();
this._register(state.currentDocumentOnly.onDidChange(this.updateFilterActiveState, this));
}
/**
@ -108,10 +100,10 @@ export class TestingExplorerFilter extends BaseActionViewItem {
placeholder: localize('testExplorerFilter', "Filter (e.g. text, !exclude)"),
history: this.history.get([]),
}));
input.value = this.state.value;
input.value = this.state.text.value;
this._register(attachInputBoxStyler(input, this.themeService));
this._register(this.state.onDidChange(newValue => {
this._register(this.state.text.onDidChange(newValue => {
input.value = newValue;
}));
@ -121,7 +113,7 @@ export class TestingExplorerFilter extends BaseActionViewItem {
this._register(input.onDidChange(() => updateDelayer.trigger(() => {
input.addToHistory();
this.state.value = input.value;
this.state.text.value = input.value;
})));
this._register(dom.addStandardDisposableListener(input.inputElement, dom.EventType.KEY_DOWN, e => {
@ -131,6 +123,16 @@ export class TestingExplorerFilter extends BaseActionViewItem {
e.preventDefault();
}
}));
const actionbar = this._register(new ActionBar(container, {
actionViewItemProvider: action => {
if (action.id === this.filtersAction.id) {
return this.instantiationService.createInstance(FiltersDropdownMenuActionViewItem, action, this.state, this.actionRunner);
}
return undefined;
}
}));
actionbar.push(this.filtersAction, { icon: true, label: false });
}
@ -160,6 +162,59 @@ export class TestingExplorerFilter extends BaseActionViewItem {
this.saveState();
super.dispose();
}
/**
* Updates the 'checked' state of the filter submenu.
*/
private updateFilterActiveState() {
this.filtersAction.checked = this.state.currentDocumentOnly.value;
}
}
class FiltersDropdownMenuActionViewItem extends DropdownMenuActionViewItem {
constructor(
action: IAction,
private readonly filters: ITestExplorerFilterState,
actionRunner: IActionRunner,
@IContextMenuService contextMenuService: IContextMenuService
) {
super(action,
{ getActions: () => this.getActions() },
contextMenuService,
{
actionRunner,
classNames: action.class,
anchorAlignmentProvider: () => AnchorAlignment.RIGHT,
menuAsChild: true
}
);
}
render(container: HTMLElement): void {
super.render(container);
this.updateChecked();
}
private getActions(): IAction[] {
return [
{
checked: this.filters.currentDocumentOnly.value,
class: undefined,
enabled: true,
id: 'currentDocument',
label: localize('testing.filters.currentFile', "Show in Active File Only"),
run: async () => this.filters.currentDocumentOnly.value = !this.filters.currentDocumentOnly.value,
tooltip: '',
dispose: () => null
},
];
}
updateChecked(): void {
this.element!.classList.toggle('checked', this._action.checked);
}
}
registerAction2(class extends Action2 {
@ -177,3 +232,18 @@ registerAction2(class extends Action2 {
}
async run(): Promise<void> { }
});
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
const inputActiveOptionBorderColor = theme.getColor(inputActiveOptionBorder);
if (inputActiveOptionBorderColor) {
collector.addRule(`.testing-filter-button.checked { border-color: ${inputActiveOptionBorderColor}; }`);
}
const inputActiveOptionForegroundColor = theme.getColor(inputActiveOptionForeground);
if (inputActiveOptionForegroundColor) {
collector.addRule(`.testing-filter-button.checked { color: ${inputActiveOptionForegroundColor}; }`);
}
const inputActiveOptionBackgroundColor = theme.getColor(inputActiveOptionBackground);
if (inputActiveOptionBackgroundColor) {
collector.addRule(`.testing-filter-button.checked { background-color: ${inputActiveOptionBackgroundColor}; }`);
}
});

View file

@ -20,6 +20,7 @@ import { FuzzyScore } from 'vs/base/common/filters';
import { splitGlobAware } from 'vs/base/common/glob';
import { Iterable } from 'vs/base/common/iterator';
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import 'vs/css!./media/testing';
import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
@ -64,7 +65,7 @@ import { DebugAction, RunAction } from './testExplorerActions';
export class TestingExplorerView extends ViewPane {
public viewModel!: TestingExplorerViewModel;
private filterActionBar = this._register(new MutableDisposable());
private currentSubscription?: TestSubscriptionListener;
private readonly currentSubscription = new MutableDisposable<TestSubscriptionListener>();
private container!: HTMLElement;
private finishDiscovery?: () => void;
private readonly location = TestingContextKeys.explorerLocation.bindTo(this.contextKeyService);;
@ -112,17 +113,16 @@ export class TestingExplorerView extends ViewPane {
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.viewModel = this.instantiationService.createInstance(TestingExplorerViewModel, listContainer, this.onDidChangeBodyVisibility, this.currentSubscription.value);
this._register(this.viewModel);
this._register(this.onDidChangeBodyVisibility(visible => {
if (!visible && this.currentSubscription) {
this.currentSubscription.dispose();
this.currentSubscription = undefined;
this.currentSubscription.value = undefined;
this.viewModel.replaceSubscription(undefined);
} else if (visible && !this.currentSubscription) {
this.currentSubscription = this.createSubscription();
this.viewModel.replaceSubscription(this.currentSubscription);
} else if (visible && !this.currentSubscription.value) {
this.currentSubscription.value = this.createSubscription();
this.viewModel.replaceSubscription(this.currentSubscription.value);
}
}));
}
@ -227,7 +227,6 @@ export class TestingExplorerViewModel extends Disposable {
@ITestExplorerFilterState filterState: TestExplorerFilterState,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IEditorService private readonly editorService: IEditorService,
@ICodeEditorService codeEditorService: ICodeEditorService,
@IStorageService private readonly storageService: IStorageService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@ITestResultService private readonly testResults: ITestResultService,
@ -239,13 +238,7 @@ export class TestingExplorerViewModel extends Disposable {
const labels = this._register(instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: onDidChangeVisibility }));
this.filter = new TestsFilter(filterState.value);
this._register(filterState.onDidChange(text => {
this.filter.setFilter(text);
this.tree.refilter();
}));
this.filter = this.instantiationService.createInstance(TestsFilter);
this.tree = instantiationService.createInstance(
WorkbenchObjectTree,
'Test Explorer List',
@ -263,11 +256,22 @@ export class TestingExplorerViewModel extends Disposable {
accessibilityProvider: instantiationService.createInstance(ListAccessibilityProvider),
filter: this.filter,
}) as WorkbenchObjectTree<ITestTreeElement, FuzzyScore>;
this._register(Event.any(filterState.currentDocumentOnly.onDidChange, filterState.text.onDidChange)(this.tree.refilter, this.tree));
this._register(editorService.onDidActiveEditorChange(() => {
if (filterState.currentDocumentOnly.value && editorService.activeEditor?.resource) {
if (this.projection.hasTestInDocument(editorService.activeEditor.resource)) {
this.filter.filterToUri(editorService.activeEditor.resource);
this.tree.refilter();
}
}
}));
this._register(this.tree);
this._register(dom.addStandardDisposableListener(this.tree.getHTMLElement(), 'keydown', evt => {
if (DefaultKeyboardNavigationDelegate.mightProducePrintableCharacter(evt)) {
filterState.value = evt.browserEvent.key;
filterState.text.value = evt.browserEvent.key;
filterState.focusInput();
}
}));
@ -282,7 +286,7 @@ export class TestingExplorerViewModel extends Disposable {
}
}));
const tracker = this._register(new CodeEditorTracker(codeEditorService, this));
const tracker = this._register(this.instantiationService.createInstance(CodeEditorTracker, this));
this._register(onDidChangeVisibility(visible => {
if (visible) {
tracker.activate();
@ -447,7 +451,10 @@ class CodeEditorTracker {
private store = new DisposableStore();
private lastRevealed?: ITestTreeElement;
constructor(@ICodeEditorService private readonly codeEditorService: ICodeEditorService, private readonly model: TestingExplorerViewModel) {
constructor(
private readonly model: TestingExplorerViewModel,
@ICodeEditorService private readonly codeEditorService: ICodeEditorService,
) {
}
public activate() {
@ -496,22 +503,23 @@ class CodeEditorTracker {
}
const enum FilterResult {
Include,
Exclude,
Inherit,
Include,
}
class TestsFilter implements ITreeFilter<ITestTreeElement> {
private lastText?: string;
private filters: [include: boolean, value: string][] | undefined;
private _filterToUri: string | undefined;
constructor(initialFilter: string) {
this.setFilter(initialFilter);
}
constructor(@ITestExplorerFilterState private readonly state: ITestExplorerFilterState) { }
/**
* Parses and updates the tree filter. Supports lists of patterns that can be !negated.
*/
public setFilter(text: string) {
private setFilter(text: string) {
this.lastText = text;
text = text.trim();
if (!text) {
@ -529,37 +537,68 @@ class TestsFilter implements ITreeFilter<ITestTreeElement> {
}
}
public filter(element: ITestTreeElement): TreeFilterResult<void> {
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 TreeVisibility.Recurse;
public filterToUri(uri: URI) {
this._filterToUri = uri.toString();
}
private testFilterText(data: string) {
/**
* @inheritdoc
*/
public filter(element: ITestTreeElement): TreeFilterResult<void> {
if (this.state.text.value !== this.lastText) {
this.setFilter(this.state.text.value);
}
switch (Math.min(this.testFilterText(element), this.testLocation(element))) {
case FilterResult.Exclude:
return TreeVisibility.Hidden;
case FilterResult.Include:
return TreeVisibility.Visible;
default:
return TreeVisibility.Recurse;
}
}
private testLocation(element: ITestTreeElement): FilterResult {
if (!this._filterToUri || !this.state.currentDocumentOnly.value) {
return FilterResult.Include;
}
for (let e: ITestTreeElement | null = element; e; e = e!.parentItem) {
if (!e.location) {
continue;
}
return e.location.uri.toString() === this._filterToUri
? FilterResult.Include
: FilterResult.Exclude;
}
return FilterResult.Inherit;
}
private testFilterText(element: ITestTreeElement) {
if (!this.filters) {
return FilterResult.Include;
}
// start as included if the first glob is a negation
let included = this.filters[0][0] === false ? FilterResult.Exclude : FilterResult.Inherit;
data = data.toLowerCase();
for (let e: ITestTreeElement | null = element; e; e = e.parentItem) {
// start as included if the first glob is a negation
let included = this.filters[0][0] === false ? FilterResult.Exclude : FilterResult.Inherit;
const data = e.label.toLowerCase();
for (const [include, filter] of this.filters) {
if (data.includes(filter)) {
included = include ? FilterResult.Include : FilterResult.Exclude;
for (const [include, filter] of this.filters) {
if (data.includes(filter)) {
included = include ? FilterResult.Include : FilterResult.Exclude;
}
}
if (included !== FilterResult.Inherit) {
return included;
}
}
return included;
return FilterResult.Inherit;
}
}
@ -589,10 +628,19 @@ class ListAccessibilityProvider implements IListAccessibilityProvider<ITestTreeE
}
getAriaLabel(element: ITestTreeElement): string {
return localize({
let label = localize({
key: 'testing.treeElementLabel',
comment: ['label then the unit tests state, for example "Addition Tests (Running)"'],
}, '{0} ({1})', element.label, testStateNames[element.state]);
if (element.retired) {
label = localize({
key: 'testing.treeElementLabelOutdated',
comment: ['{0} is the original label in testing.treeElementLabel'],
}, '{0}, outdated result', label, testStateNames[element.state]);
}
return label;
}
}
@ -669,6 +717,10 @@ class TestsRenderer implements ITreeRenderer<ITestTreeElement, FuzzyScore, TestT
const icon = testingStatesToIcons.get(element.state);
data.icon.className = 'computed-state ' + (icon ? ThemeIcon.asClassName(icon) : '');
if (element.retired) {
data.icon.className += ' retired';
}
const test = element.test;
if (test) {
if (test.item.location) {

View file

@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter } from 'vs/base/common/event';
import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue';
export class ObservableValue<T> {
private readonly changeEmitter = new Emitter<T>();
public readonly onDidChange = this.changeEmitter.event;
public get value() {
return this._value;
}
public set value(v: T) {
if (v !== this._value) {
this._value = v;
this.changeEmitter.fire(v);
}
}
public static stored<T>(stored: StoredValue<T>, defaultValue: T) {
const o = new ObservableValue(stored.get(defaultValue));
o.onDidChange(value => stored.store(value));
return o;
}
constructor(private _value: T) { }
}

View file

@ -93,6 +93,7 @@ const itemToNode = (
item: { ...item.item },
state: unsetState,
computedState: TestRunState.Unset,
retired: false,
};
byExtId.set(n.item.extId, n);
@ -154,6 +155,7 @@ interface ISerializedResults {
interface TestResultItem extends IncrementalTestCollectionItem {
state: ITestState;
computedState: TestRunState;
retired: boolean;
}
/**
@ -300,6 +302,28 @@ export class LiveTestResult implements ITestResult {
}
}
/**
* Marks a test as retired. This can trigger it to be re-run in live mode.
*/
public retire(extId: string) {
const root = this.testByExtId.get(extId);
if (!root || root.retired) {
return;
}
const queue: Iterable<string>[] = [[root.id]];
while (queue.length) {
for (const id of queue.pop()!) {
const entry = this.testByInternalId.get(id);
if (entry && !entry.retired) {
entry.retired = true;
queue.push(entry.children);
this.changeEmitter.fire(entry);
}
}
}
}
/**
* Adds a test, by its ID, to the test run. This can end up being called
* if tests were started while discovery was still happening, so initially
@ -363,6 +387,7 @@ class HydratedTestResult implements ITestResult {
for (const [key, value] of serialized.items) {
this.map.set(key, value);
value.retired = true;
for (const message of value.state.messages) {
if (message.location) {
message.location.uri = URI.revive(message.location.uri);

View file

@ -434,7 +434,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu
private registerGlobalActivityActions(): void {
CommandsRegistry.registerCommand('update.check', () => this.updateService.checkForUpdates(this.environmentService.sessionId));
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '6_update',
group: '7_update',
command: {
id: 'update.check',
title: nls.localize('checkForUpdates', "Check for Updates...")
@ -444,7 +444,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu
CommandsRegistry.registerCommand('update.checking', () => { });
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '6_update',
group: '7_update',
command: {
id: 'update.checking',
title: nls.localize('checkingForUpdates', "Checking for Updates..."),
@ -455,7 +455,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu
CommandsRegistry.registerCommand('update.downloadNow', () => this.updateService.downloadUpdate());
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '6_update',
group: '7_update',
command: {
id: 'update.downloadNow',
title: nls.localize('download update_1', "Download Update (1)")
@ -465,7 +465,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu
CommandsRegistry.registerCommand('update.downloading', () => { });
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '6_update',
group: '7_update',
command: {
id: 'update.downloading',
title: nls.localize('DownloadingUpdate', "Downloading Update..."),
@ -476,7 +476,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu
CommandsRegistry.registerCommand('update.install', () => this.updateService.applyUpdate());
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '6_update',
group: '7_update',
command: {
id: 'update.install',
title: nls.localize('installUpdate...', "Install Update... (1)")
@ -486,7 +486,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu
CommandsRegistry.registerCommand('update.updating', () => { });
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '6_update',
group: '7_update',
command: {
id: 'update.updating',
title: nls.localize('installingUpdate', "Installing Update..."),
@ -497,7 +497,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu
CommandsRegistry.registerCommand('update.restart', () => this.updateService.quitAndInstall());
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '6_update',
group: '7_update',
command: {
id: 'update.restart',
title: nls.localize('restartToUpdate', "Restart to Update (1)")
@ -541,7 +541,7 @@ export class SwitchProductQualityContribution extends Disposable implements IWor
}
});
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '6_update',
group: '7_update',
command: {
id: commandId,
title: newQuality === 'insider' ? nls.localize('switchToInsiders', "Switch to Insiders Version...") : nls.localize('switchToStable', "Switch to Stable Version...")

View file

@ -4,16 +4,17 @@
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { GettingStartedInputFactory, GettingStartedInput, getGettingStartedInput } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted';
import { GettingStartedInputFactory, GettingStartedInput, GettingStartedPage } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor';
import { MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration';
import product from 'vs/platform/product/common/product';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { KeyCode } from 'vs/base/common/keyCodes';
import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
export * as icons from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedIcons';
@ -24,10 +25,8 @@ registerAction2(class extends Action2 {
title: localize('Getting Started', "Getting Started"),
category: localize('help', "Help"),
f1: true,
precondition: ContextKeyExpr.has('config.workbench.experimental.gettingStarted'),
menu: {
id: MenuId.MenubarHelpMenu,
when: ContextKeyExpr.has('config.workbench.experimental.gettingStarted'),
group: '1_welcome',
order: 2,
}
@ -35,21 +34,94 @@ registerAction2(class extends Action2 {
}
public run(accessor: ServicesAccessor) {
accessor.get(IEditorService).openEditor(accessor.get(IInstantiationService).invokeFunction(getGettingStartedInput, {}), {});
accessor.get(IEditorService).openEditor(new GettingStartedInput({}), {});
}
});
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(GettingStartedInput.ID, GettingStartedInputFactory);
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
EditorDescriptor.create(
GettingStartedPage,
GettingStartedPage.ID,
localize('gettingStarted', "Getting Started")
),
[
new SyncDescriptor(GettingStartedInput)
]
);
if (product.quality !== 'stable') {
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({
...workbenchConfigurationNodeBase,
properties: {
'workbench.experimental.gettingStarted': {
type: 'boolean',
description: localize('gettingStartedDescription', "Enables an experimental Getting Started page, available via the Help menu."),
default: false,
}
const category = localize('gettingStarted', "Getting Started");
registerAction2(class extends Action2 {
constructor() {
super({
id: 'gettingStarted.goBack',
title: localize('gettingStarted.goBack', "Go Back"),
category,
keybinding: {
weight: KeybindingWeight.EditorContrib,
primary: KeyCode.Escape,
},
precondition: ContextKeyEqualsExpr.create('activeEditor', 'gettingStartedPage'),
f1: true
});
}
run(accessor: ServicesAccessor) {
const editorService = accessor.get(IEditorService);
const editorPane = editorService.activeEditorPane;
if (editorPane instanceof GettingStartedPage) {
editorPane.escape();
}
});
}
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: 'gettingStarted.next',
title: localize('gettingStarted.goNext', "Next"),
category,
keybinding: {
weight: KeybindingWeight.EditorContrib,
primary: KeyCode.DownArrow,
secondary: [KeyCode.RightArrow],
},
precondition: ContextKeyEqualsExpr.create('activeEditor', 'gettingStartedPage'),
f1: true
});
}
run(accessor: ServicesAccessor) {
const editorService = accessor.get(IEditorService);
const editorPane = editorService.activeEditorPane;
if (editorPane instanceof GettingStartedPage) {
editorPane.focusNext();
}
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: 'gettingStarted.prev',
title: localize('gettingStarted.goPrev', "Previous"),
category,
keybinding: {
weight: KeybindingWeight.EditorContrib,
primary: KeyCode.UpArrow,
secondary: [KeyCode.LeftArrow],
},
precondition: ContextKeyEqualsExpr.create('activeEditor', 'gettingStartedPage'),
f1: true
});
}
run(accessor: ServicesAccessor) {
const editorService = accessor.get(IEditorService);
const editorPane = editorService.activeEditorPane;
if (editorPane instanceof GettingStartedPage) {
editorPane.focusPrevious();
}
}
});

View file

@ -3,12 +3,29 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.file-icons-enabled .show-file-icons .vs_code_editor_getting_started\.md-name-file-icon.md-ext-file-icon.ext-file-icon.markdown-lang-file-icon.file-icon::before {
.file-icons-enabled .show-file-icons .vscode_getting_started_page-name-file-icon.file-icon::before {
content: ' ';
background-image: url('../../../../browser/media/code-icon.svg');
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide {
.monaco-workbench .part.editor > .content .gettingStartedContainer {
box-sizing: border-box;
padding: 10px 20px;
line-height: 22px;
position: relative;
overflow: hidden;
height: inherit;
width: 100%;
user-select: initial;
-webkit-user-select: initial;
}
.monaco-workbench .part.editor > .content .gettingStartedContainer img {
max-width: 100%;
max-height: 100%;
}
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide {
width: 100%;
height: 100%;
position: absolute;
@ -16,53 +33,54 @@
top: 0;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer.animationReady .gettingStartedSlide {
.monaco-workbench .part.editor > .content .gettingStartedContainer.animationReady .gettingStartedSlide {
/* keep consistant with SLIDE_TRANSITION_TIME_MS in gettingStarted.ts */
transition: left 0.25s, opacity 0.25s;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories {
display: flex;
flex-direction: column;
overflow: hidden;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .gap {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .gap {
flex: 150px 0 1000
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .header {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .header {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .category-title {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .category-title {
margin-bottom: 4px;
font-weight: 600;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .category-description-container {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .category-description-container {
width: 100%
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .category-progress {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .category-progress {
margin-top: 12px;
font-size: 12px;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .progress-bar-outer {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .progress-bar-outer {
height: 8px;
border-radius: 4px;
margin-top: 4px;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .progress-bar-inner {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .progress-bar-inner {
height: 100%;
border-radius: 4px;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .getting-started-categories-container {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .getting-started-categories-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
@ -70,12 +88,12 @@
margin: 32px auto;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .getting-started-categories-scrolling-container {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .getting-started-categories-scrolling-container {
overflow: scroll;
height: 100%;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide .getting-started-category {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide .getting-started-category {
width: 330px;
min-height: 80px;
font-size: 13px;
@ -85,26 +103,26 @@
display: flex;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .getting-started-category {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .getting-started-category {
padding-right: 46px;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide .getting-started-category .codicon {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide .getting-started-category .codicon {
margin-right: 10px;
font-size: 32px;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail {
display: flex;
flex-direction: column;
overflow: hidden;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .gap {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .gap {
flex: 150px 0 1000
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-category {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-category {
width: 330px;
display: flex;
padding: 10px 0 20px 12px;
@ -112,23 +130,23 @@
min-height: auto;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-detail-columns .gap {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-detail-columns .gap {
flex: 150px 1 1000
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-category .codicon {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-category .codicon {
margin-left:0;
font-size: 22pt;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-detail-columns {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-detail-columns {
display: flex;
justify-content: flex-start;
padding: 40px 40px 0;
max-height: calc(100% - 40px);
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task {
display: flex;
width: 100%;
overflow: hidden;
@ -136,56 +154,57 @@
max-height: 180px;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) {
max-height: 54px;
background: none;
opacity: 0.8;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-detail-columns .getting-started-detail-left > div {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-detail-columns .getting-started-detail-left > div {
width: 100%;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) .task-description,
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) .actions,
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) button,
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) a {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) .task-description,
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) .image-description,
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) .actions,
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) button,
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) a {
display: none;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .task-description {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .task-description {
padding-top: 8px;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .actions {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .actions {
margin-top: 12px;
display: flex;
align-items: center;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .task-next {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .task-next {
margin-left: auto;
margin-right: 10px;
padding: 6px 12px;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .codicon.hidden {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .codicon.hidden {
display: none;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .codicon {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .codicon {
margin-right: 8px;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task-action {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task-action {
padding: 6px 12px;
font-size: 13px;
margin-bottom: 0;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-detail-left {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-detail-left {
min-width: 330px;
width: 40%;
max-width: 400px;
@ -193,21 +212,21 @@
flex-direction: column;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .full-height-scrollable {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .full-height-scrollable {
height: 100%;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-detail-container {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-detail-container {
height: 100%;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .gettingStartedDetailsContent {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .gettingStartedDetailsContent {
height: 100%;
display: flex;
flex-direction: column;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-detail-right {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-detail-right {
display: flex;
align-items: flex-start;
justify-content: center;
@ -218,11 +237,11 @@
max-width: 800px;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-detail-right img {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-detail-right img {
object-fit: contain;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button {
.monaco-workbench .part.editor > .content .gettingStartedContainer button {
border: none;
color: inherit;
text-align: left;
@ -230,15 +249,15 @@
margin: 1px 0; /* makes room for focus border */
font-family: inherit;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button:hover {
.monaco-workbench .part.editor > .content .gettingStartedContainer button:hover {
cursor: pointer;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button:focus {
.monaco-workbench .part.editor > .content .gettingStartedContainer button:focus {
outline-style: solid;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .prev-button.button-link {
.monaco-workbench .part.editor > .content .gettingStartedContainer .prev-button.button-link {
position: absolute;
left: 40px;
top: 5px;
@ -247,24 +266,24 @@
z-index: 1;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .prev-button:hover {
.monaco-workbench .part.editor > .content .gettingStartedContainer .prev-button:hover {
cursor: pointer;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .prev-button .codicon {
.monaco-workbench .part.editor > .content .gettingStartedContainer .prev-button .codicon {
position: relative;
top: 3px;
left: -4px;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide .skip {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide .skip {
display: block;
margin: 2px auto;
width: fit-content;
text-align: center;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide h1 {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide h1 {
font-size: 32px;
font-weight: normal;
border-bottom: none;
@ -272,42 +291,42 @@
padding: 0;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide h2 {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide h2 {
font-weight: normal;
line-height: 26px;
margin: 0 0 4px 0;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide h3 {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide h3 {
font-size: 13px;
font-weight: 700;
margin: 0;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide .subtitle {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide .subtitle {
font-size: 16px;
margin: 0;
padding: 0;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.next {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStarted.showCategories .detail {
left: 100%;
opacity: 0;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.prev {
.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStarted.showDetails .categories {
left: -100%;
opacity: 0;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .button-link {
.monaco-workbench .part.editor > .content .gettingStartedContainer .button-link {
padding: 0;
background: transparent;
margin: 2px;
cursor: pointer;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .button-link:hover {
.monaco-workbench .part.editor > .content .gettingStartedContainer .button-link:hover {
text-decoration: underline;
background: transparent;
}

View file

@ -4,15 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./gettingStarted';
import 'vs/workbench/contrib/welcome/gettingStarted/browser/vs_code_editor_getting_started';
import { localize } from 'vs/nls';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { WalkThroughInput, WalkThroughInputOptions } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput';
import { FileAccess, Schemas } from 'vs/base/common/network';
import { IEditorInputFactory } from 'vs/workbench/common/editor';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { EditorInput, EditorOptions, IEditorInputFactory, IEditorOpenContext } from 'vs/workbench/common/editor';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { assertIsDefined } from 'vs/base/common/types';
import { $, addDisposableListener } from 'vs/base/browser/dom';
import { $, addDisposableListener, reset } from 'vs/base/browser/dom';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IProductService } from 'vs/platform/product/common/productService';
import { IGettingStartedCategoryWithProgress, IGettingStartedService } from 'vs/workbench/services/gettingStarted/common/gettingStartedService';
@ -23,62 +20,56 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { gettingStartedCheckedCodicon, gettingStartedUncheckedCodicon } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedIcons';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { URI } from 'vs/base/common/uri';
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Schemas } from 'vs/base/common/network';
const SLIDE_TRANSITION_TIME_MS = 250;
export const gettingStartedInputTypeId = 'workbench.editors.gettingStartedInput';
const telemetryFrom = 'gettingStartedPage';
export class GettingStartedInput extends WalkThroughInput {
export class GettingStartedInput extends EditorInput {
get resource(): URI | undefined {
return URI.from({ scheme: Schemas.walkThrough, authority: 'vscode_getting_started_page' });
}
getTypeId(): string {
return GettingStartedInput.ID;
}
matches(other: unknown) {
if (other instanceof GettingStartedInput) {
return true;
}
return false;
}
static readonly ID = gettingStartedInputTypeId;
constructor(
options: WalkThroughInputOptions & { selectedCategory?: string, selectedTask?: string },
@ITextModelService textModelResolverService: ITextModelService
options: { selectedCategory?: string, selectedTask?: string }
) {
super(options, textModelResolverService);
super();
this.selectedCategory = options.selectedCategory;
this.selectedTask = options.selectedTask;
}
getName() {
return localize('gettingStarted', "Getting Started");
}
selectedCategory: string | undefined;
selectedTask: string | undefined;
}
export function getGettingStartedInput(accessor: ServicesAccessor, options: { selectedCategory?: string, selectedTask?: string }) {
const resource = FileAccess.asBrowserUri('./vs_code_editor_getting_started.md', require)
.with({
scheme: Schemas.walkThrough,
query: JSON.stringify({ moduleId: 'vs/workbench/contrib/welcome/gettingStarted/browser/vs_code_editor_getting_started' })
});
export class GettingStartedPage extends EditorPane {
const instantiationService = accessor.get(IInstantiationService);
public static ID = 'gettingStartedPage';
const pages: GettingStartedPage[] = [];
const editorInput = instantiationService.createInstance(GettingStartedInput, {
typeId: gettingStartedInputTypeId,
name: localize('editorGettingStarted.title', "Getting Started"),
resource,
telemetryFrom,
selectedCategory: options.selectedCategory,
selectedTask: options.selectedTask,
onReady: (container: HTMLElement, disposableStore: DisposableStore) => {
const page = instantiationService.createInstance(GettingStartedPage, editorInput);
page.onReady(container);
pages.push(page);
disposableStore.add(page);
},
layout: () => pages.forEach(page => page.layout()),
});
return editorInput;
}
export class GettingStartedPage extends Disposable {
readonly editorInput: GettingStartedInput;
private editorInput!: GettingStartedInput;
private inProgressScroll = Promise.resolve();
private dispatchListeners: DisposableStore = new DisposableStore();
@ -91,19 +82,22 @@ export class GettingStartedPage extends Disposable {
private detailsScrollbar: DomScrollableElement | undefined;
private detailImageScrollbar: DomScrollableElement | undefined;
private container: HTMLElement;
constructor(
editorInput: GettingStartedInput,
@ICommandService private readonly commandService: ICommandService,
@IProductService private readonly productService: IProductService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@IGettingStartedService private readonly gettingStartedService: IGettingStartedService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@ITelemetryService telemetryService: ITelemetryService,
@IOpenerService private readonly openerService: IOpenerService,
@IThemeService private readonly themeService: IThemeService,
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService,
) {
super();
this.editorInput = editorInput;
super(GettingStartedPage.ID, telemetryService, themeService, storageService);
this.container = $('.gettingStartedContainer');
this.gettingStartedCategories = this.gettingStartedService.getCategories();
this._register(this.dispatchListeners);
@ -135,10 +129,18 @@ export class GettingStartedPage extends Disposable {
}));
}
private registerDispatchListeners(container: HTMLElement) {
async setInput(newInput: GettingStartedInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken) {
this.container.classList.remove('animationReady');
this.editorInput = newInput;
await super.setInput(newInput, options, context, token);
this.buildCategoriesSlide();
setTimeout(() => this.container.classList.add('animationReady'), 0);
}
private registerDispatchListeners() {
this.dispatchListeners.clear();
container.querySelectorAll('[x-dispatch]').forEach(element => {
this.container.querySelectorAll('[x-dispatch]').forEach(element => {
const [command, argument] = (element.getAttribute('x-dispatch') ?? '').split(':');
if (command) {
this.dispatchListeners.add(addDisposableListener(element, 'click', (e) => {
@ -157,11 +159,11 @@ export class GettingStartedPage extends Disposable {
switch (command) {
case 'scrollPrev': {
this.scrollPrev(container);
this.scrollPrev();
break;
}
case 'skip': {
this.commandService.executeCommand('workbench.action.closeActiveEditor');
this.runSkip();
break;
}
case 'selectCategory': {
@ -170,12 +172,12 @@ export class GettingStartedPage extends Disposable {
if (selectedCategory.content.type === 'command') {
this.commandService.executeCommand(selectedCategory.content.command);
} else {
this.scrollToCategory(container, argument);
this.scrollToCategory(argument);
}
break;
}
case 'selectTask': {
this.selectTask(container, argument);
this.selectTask(argument);
e.stopPropagation();
break;
}
@ -205,13 +207,16 @@ export class GettingStartedPage extends Disposable {
});
}
private selectTask(container: HTMLElement, id: string | undefined, contractIfAlreadySelected = true) {
const mediaElement = assertIsDefined(container.querySelector('.getting-started-media') as HTMLImageElement);
private selectTask(id: string | undefined, contractIfAlreadySelected = true, delayFocus = true) {
const mediaElement = assertIsDefined(this.container.querySelector('.getting-started-media') as HTMLImageElement);
this.taskDisposables.clear();
if (id) {
const taskElement = assertIsDefined(container.querySelector(`[data-task-id="${id}"]`));
taskElement.parentElement?.querySelectorAll('.expanded').forEach(node => node.classList.remove('expanded'));
(taskElement as HTMLDivElement).focus();
const taskElement = assertIsDefined(this.container.querySelector(`[data-task-id="${id}"]`));
taskElement.parentElement?.querySelectorAll('.expanded').forEach(node => {
node.classList.remove('expanded');
node.setAttribute('aria-expanded', 'false');
});
setTimeout(() => (taskElement as HTMLDivElement).focus(), delayFocus ? SLIDE_TRANSITION_TIME_MS : 0);
if (this.editorInput.selectedTask === id && contractIfAlreadySelected) {
this.editorInput.selectedTask = undefined;
return;
@ -228,6 +233,7 @@ export class GettingStartedPage extends Disposable {
this.taskDisposables.add(addDisposableListener(mediaElement, 'click', () => taskElement.querySelector('button')?.click()));
this.taskDisposables.add(this.themeService.onDidColorThemeChange(() => this.updateMediaSourceForColorMode(mediaElement, taskToExpand.media.path)));
taskElement.classList.add('expanded');
taskElement.setAttribute('aria-expanded', 'true');
} else {
this.editorInput.selectedTask = undefined;
mediaElement.setAttribute('src', '');
@ -242,14 +248,51 @@ export class GettingStartedPage extends Disposable {
element.src = sources[themeType].toString();
}
onReady(container: HTMLElement) {
createEditor(parent: HTMLElement) {
const tasksContent =
$('.gettingStartedDetailsContent', {},
$('.gap'),
$('.getting-started-detail-columns', {},
$('.gap'),
$('.getting-started-detail-left', {},
$('.getting-started-detail-title')),
$('.getting-started-detail-right', {},
$('img.getting-started-media')),
$('.gap'),
),
$('.gap')
);
const tasksSlide =
$('.gettingStartedSlideDetails.gettingStartedSlide.detail', {},
$('button.prev-button.button-link', { 'x-dispatch': 'scrollPrev' }, $('span.scroll-button.codicon.codicon-chevron-left'), localize('back', "Back")),
tasksContent
);
const gettingStartedPage =
$('.gettingStarted', { role: 'document' },
$('.gettingStartedSlideCategory.gettingStartedSlide.categories'),
tasksSlide
);
if (this.detailImageScrollbar) { this.detailImageScrollbar.dispose(); }
this.detailImageScrollbar = this._register(new DomScrollableElement(tasksContent, { className: 'full-height-scrollable' }));
tasksSlide.appendChild(this.detailImageScrollbar.getDomNode());
this.detailImageScrollbar.scanDomNode();
this.container.appendChild(gettingStartedPage);
parent.appendChild(this.container);
}
buildCategoriesSlide() {
const categoryElements = this.gettingStartedCategories.map(
category => {
const categoryDescriptionElement =
category.content.type === 'items' ?
$('.category-description-container', {},
$('h3.category-title', {}, category.title),
$('.category-description.description', {}, category.description),
$('.category-description.description', { 'aria-label': category.description + ' ' + localize('pressEnterToSelect', "Press Enter to Select") }, category.description),
$('.category-progress', { 'x-data-category-id': category.id, },
$('.message'),
$('.progress-bar-outer', {},
@ -257,26 +300,13 @@ export class GettingStartedPage extends Disposable {
:
$('.category-description-container', {},
$('h3.category-title', {}, category.title),
$('.category-description.description', {}, category.description));
$('.category-description.description', { 'aria-label': category.description + ' ' + localize('pressEnterToSelect', "Press Enter to Select") }, category.description));
return $('button.getting-started-category',
{ 'x-dispatch': 'selectCategory:' + category.id },
{ 'x-dispatch': 'selectCategory:' + category.id, },
$(ThemeIcon.asCSSSelector(category.icon), {}), categoryDescriptionElement);
});
const categoriesSlide = assertIsDefined(container.querySelector('.gettingStartedSlideCategory'));
const tasksSlide = assertIsDefined(container.querySelector('.gettingStartedSlideDetails'));
const tasksContent = assertIsDefined(container.querySelector('.gettingStartedDetailsContent') as HTMLElement);
tasksContent.remove();
if (this.detailImageScrollbar) { this.detailImageScrollbar.dispose(); }
this.detailImageScrollbar = this._register(new DomScrollableElement(tasksContent, { className: 'full-height-scrollable' }));
tasksSlide.appendChild(this.detailImageScrollbar.getDomNode());
this.detailImageScrollbar.scanDomNode();
const rightColumn = assertIsDefined(container.querySelector('.getting-started-detail-right'));
rightColumn.appendChild($('img.getting-started-media'));
const categoryScrollContainer = $('.getting-started-categories-scrolling-container');
const categoriesContainer = $('.getting-started-categories-container');
categoryElements.forEach(element => {
@ -288,14 +318,22 @@ export class GettingStartedPage extends Disposable {
if (this.categoriesScrollbar) { this.categoriesScrollbar.dispose(); }
this.categoriesScrollbar = this._register(new DomScrollableElement(categoryScrollContainer, {}));
categoriesSlide.appendChild(this.categoriesScrollbar.getDomNode());
categoriesSlide.appendChild($('.gap'));
const categoriesSlide = assertIsDefined(this.container.querySelector('.gettingStartedSlideCategory') as HTMLElement);
reset(categoriesSlide,
$('.gap'),
$('.header', {},
$('h1.product-name.caption', {}, localize('gettingStarted.vscode', "Visual Studio Code")),
$('p.subtitle.description', {}, localize({ key: 'gettingStarted.editingRedefined', comment: ['Shown as subtitle on the Welcome page.'] }, "Code editing. Redefined")),
),
this.categoriesScrollbar.getDomNode(),
$('.gap')
);
this.categoriesScrollbar.scanDomNode();
this.updateCategoryProgress();
assertIsDefined(container.querySelector('.product-name')).textContent = this.productService.nameLong;
this.registerDispatchListeners(container);
assertIsDefined(this.container.querySelector('.product-name')).textContent = this.productService.nameLong;
this.registerDispatchListeners();
if (this.editorInput.selectedCategory) {
@ -303,15 +341,12 @@ export class GettingStartedPage extends Disposable {
if (!this.currentCategory) {
throw Error('Could not restore to category ' + this.editorInput.selectedCategory + ' as it was not found');
}
this.buildCategorySlide(container, this.editorInput.selectedCategory, this.editorInput.selectedTask);
categoriesSlide.classList.add('prev');
this.setButtonEnablement(container, 'details');
this.setSlide('details');
this.buildCategorySlide(this.editorInput.selectedCategory, this.editorInput.selectedTask);
} else {
tasksSlide.classList.add('next');
this.focusFirstUncompletedCategory(container);
this.setButtonEnablement(container, 'categories');
this.focusFirstUncompletedCategory();
this.setSlide('categories');
}
setTimeout(() => assertIsDefined(container.querySelector('.gettingStartedContainer')).classList.add('animationReady'), 0);
}
layout() {
@ -342,29 +377,26 @@ export class GettingStartedPage extends Disposable {
});
}
private async scrollToCategory(container: HTMLElement, categoryID: string) {
private async scrollToCategory(categoryID: string) {
this.inProgressScroll = this.inProgressScroll.then(async () => {
this.clearDetialView(container);
this.clearDetialView();
this.editorInput.selectedCategory = categoryID;
this.currentCategory = this.gettingStartedCategories.find(category => category.id === categoryID);
const slides = [...container.querySelectorAll('.gettingStartedSlide').values()];
const currentSlide = slides.findIndex(element => !element.classList.contains('prev') && !element.classList.contains('next'));
if (currentSlide < slides.length - 1) {
this.buildCategorySlide(container, categoryID);
slides[currentSlide].classList.add('prev');
slides[currentSlide + 1].classList.remove('next');
this.setButtonEnablement(container, 'details');
}
this.buildCategorySlide(categoryID);
this.setSlide('details');
});
}
private buildCategorySlide(container: HTMLElement, categoryID: string, selectedItem?: string) {
private buildCategorySlide(categoryID: string, selectedItem?: string) {
const category = this.gettingStartedCategories.find(category => category.id === categoryID);
if (!category) { throw Error('could not find category with ID ' + categoryID); }
if (category.content.type !== 'items') { throw Error('category with ID ' + categoryID + ' is not of items type'); }
const leftColumn = assertIsDefined(container.querySelector('.getting-started-detail-left'));
const detailTitle = assertIsDefined(container.querySelector('.getting-started-detail-title'));
const leftColumn = assertIsDefined(this.container.querySelector('.getting-started-detail-left'));
const detailTitle = assertIsDefined(this.container.querySelector('.getting-started-detail-title'));
const oldTitle = detailTitle.querySelector('.getting-started-category');
if (oldTitle) { detailTitle.removeChild(oldTitle); }
detailTitle.appendChild(
$('.getting-started-category',
{},
@ -375,11 +407,16 @@ export class GettingStartedPage extends Disposable {
const categoryElements = category.content.items.map(
(task, i, arr) => $('button.getting-started-task',
{ 'x-dispatch': 'selectTask:' + task.id, 'data-task-id': task.id },
{
'x-dispatch': 'selectTask:' + task.id,
'data-task-id': task.id,
'aria-expanded': 'false',
},
$('.codicon' + (task.done ? '.complete.codicon-pass-filled' : '.codicon-circle-large-outline'), { 'data-done-task-id': task.id }),
$('.task-description-container', {},
$('h3.task-title', {}, task.title),
$('.task-description.description', {}, task.description),
$('.image-description', { 'aria-label': localize('imageShowing', "Image showing {0}", task.media.altText) }),
$('.actions', {},
...(
task.button
@ -403,15 +440,15 @@ export class GettingStartedPage extends Disposable {
leftColumn.appendChild(this.detailsScrollbar.getDomNode());
const toExpand = category.content.items.find(item => !item.done) ?? category.content.items[0];
this.selectTask(container, selectedItem ?? toExpand.id, false);
this.selectTask(selectedItem ?? toExpand.id, false);
this.detailsScrollbar.scanDomNode();
this.registerDispatchListeners(container);
this.registerDispatchListeners();
}
private clearDetialView(container: HTMLElement) {
const detailContainer = (container.querySelector('.getting-started-detail-container'));
private clearDetialView() {
const detailContainer = (this.container.querySelector('.getting-started-detail-container'));
detailContainer?.remove();
const detailTitle = assertIsDefined(container.querySelector('.getting-started-detail-title'));
const detailTitle = assertIsDefined(this.container.querySelector('.getting-started-detail-title'));
while (detailTitle.firstChild) { detailTitle.removeChild(detailTitle.firstChild); }
}
@ -421,40 +458,74 @@ export class GettingStartedPage extends Disposable {
else { return ` (${binding.getLabel()})`; }
}
private async scrollPrev(container: HTMLElement) {
private async scrollPrev() {
this.inProgressScroll = this.inProgressScroll.then(async () => {
this.currentCategory = undefined;
this.editorInput.selectedCategory = undefined;
this.editorInput.selectedTask = undefined;
this.selectTask(container, undefined);
const slides = [...container.querySelectorAll('.gettingStartedSlide').values()];
const currentSlide = slides.findIndex(element =>
!element.classList.contains('prev') && !element.classList.contains('next'));
if (currentSlide > 0) {
slides[currentSlide].classList.add('next');
assertIsDefined(slides[currentSlide - 1]).classList.remove('prev');
this.setButtonEnablement(container, 'categories');
}
this.focusFirstUncompletedCategory(container);
this.selectTask(undefined);
this.setSlide('categories');
this.focusFirstUncompletedCategory();
});
}
private focusFirstUncompletedCategory(container: HTMLElement) {
private runSkip() {
this.commandService.executeCommand('workbench.action.closeActiveEditor');
}
escape() {
if (this.editorInput.selectedCategory) {
this.scrollPrev();
} else {
this.runSkip();
}
}
focusNext() {
if (this.editorInput.selectedCategory) {
const allTasks = this.currentCategory?.content.type === 'items' && this.currentCategory.content.items;
if (allTasks) {
const selectedIndex = allTasks.findIndex(task => task.id === this.editorInput.selectedTask);
if (allTasks[selectedIndex + 1]?.id) { this.selectTask(allTasks[selectedIndex + 1]?.id, true, false); }
}
} else {
(document.activeElement?.nextElementSibling as HTMLElement)?.focus?.();
}
}
focusPrevious() {
if (this.editorInput.selectedCategory) {
const allTasks = this.currentCategory?.content.type === 'items' && this.currentCategory.content.items;
if (allTasks) {
const selectedIndex = allTasks.findIndex(task => task.id === this.editorInput.selectedTask);
if (allTasks[selectedIndex - 1]?.id) { this.selectTask(allTasks[selectedIndex - 1]?.id, true, false); }
}
} else {
(document.activeElement?.previousElementSibling as HTMLElement)?.focus?.();
}
}
private focusFirstUncompletedCategory() {
let toFocus!: HTMLElement;
container.querySelectorAll('.category-progress').forEach(progress => {
this.container.querySelectorAll('.category-progress').forEach(progress => {
const progressAmount = assertIsDefined(progress.querySelector('.progress-bar-inner') as HTMLDivElement).style.width;
if (!toFocus && progressAmount !== '100%') { toFocus = assertIsDefined(progress.parentElement?.parentElement); }
});
(toFocus ?? assertIsDefined(container.querySelector('button.skip')) as HTMLButtonElement).focus();
(toFocus ?? assertIsDefined(this.container.querySelector('button.skip')) as HTMLButtonElement).focus();
}
private setButtonEnablement(container: HTMLElement, toEnable: 'details' | 'categories') {
private setSlide(toEnable: 'details' | 'categories') {
const slideManager = assertIsDefined(this.container.querySelector('.gettingStarted'));
if (toEnable === 'categories') {
container.querySelector('.gettingStartedSlideDetails')!.querySelectorAll('button').forEach(button => button.disabled = true);
container.querySelector('.gettingStartedSlideCategory')!.querySelectorAll('button').forEach(button => button.disabled = false);
slideManager.classList.remove('showDetails');
slideManager.classList.add('showCategories');
this.container.querySelector('.gettingStartedSlideDetails')!.querySelectorAll('button').forEach(button => button.disabled = true);
this.container.querySelector('.gettingStartedSlideCategory')!.querySelectorAll('button').forEach(button => button.disabled = false);
} else {
container.querySelector('.gettingStartedSlideDetails')!.querySelectorAll('button').forEach(button => button.disabled = false);
container.querySelector('.gettingStartedSlideCategory')!.querySelectorAll('button').forEach(button => button.disabled = true);
slideManager.classList.add('showDetails');
slideManager.classList.remove('showCategories');
this.container.querySelector('.gettingStartedSlideDetails')!.querySelectorAll('button').forEach(button => button.disabled = false);
this.container.querySelector('.gettingStartedSlideCategory')!.querySelectorAll('button').forEach(button => button.disabled = true);
}
}
}
@ -471,9 +542,9 @@ export class GettingStartedInputFactory implements IEditorInputFactory {
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): GettingStartedInput {
try {
const { selectedCategory, selectedTask } = JSON.parse(serializedEditorInput);
return instantiationService.invokeFunction(getGettingStartedInput, { selectedCategory, selectedTask });
return new GettingStartedInput({ selectedCategory, selectedTask });
} catch { }
return instantiationService.invokeFunction(getGettingStartedInput, {});
return new GettingStartedInput({});
}
}
@ -486,85 +557,85 @@ registerThemingParticipant((theme, collector) => {
const foregroundColor = theme.getColor(foreground);
if (foregroundColor) {
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer { color: ${foregroundColor}; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer { color: ${foregroundColor}; }`);
}
const descriptionColor = theme.getColor(descriptionForeground);
if (descriptionColor) {
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .description { color: ${descriptionColor}; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .category-progress .message { color: ${descriptionColor}; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .description { color: ${descriptionColor}; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .category-progress .message { color: ${descriptionColor}; }`);
}
const iconColor = theme.getColor(textLinkForeground);
if (iconColor) {
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .getting-started-category .codicon { color: ${iconColor} }`);
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .codicon.complete { color: ${iconColor} } `);
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task.expanded .codicon { color: ${iconColor} } `);
collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .getting-started-category .codicon { color: ${iconColor} }`);
collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .codicon.complete { color: ${iconColor} } `);
collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task.expanded .codicon { color: ${iconColor} } `);
}
const buttonColor = theme.getColor(welcomePageTileBackground);
if (buttonColor) {
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button { background: ${buttonColor}; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer button { background: ${buttonColor}; }`);
}
const buttonHoverColor = theme.getColor(welcomePageTileHoverBackground);
if (buttonHoverColor) {
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button:hover { background: ${buttonHoverColor}; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer button:hover { background: ${buttonHoverColor}; }`);
}
if (buttonColor && buttonHoverColor) {
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button.expanded:hover { background: ${buttonColor}; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer button.expanded:hover { background: ${buttonColor}; }`);
}
const emphasisButtonForeground = theme.getColor(buttonForeground);
if (emphasisButtonForeground) {
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button.emphasis { color: ${emphasisButtonForeground}; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer button.emphasis { color: ${emphasisButtonForeground}; }`);
}
const emphasisButtonBackground = theme.getColor(buttonBackground);
if (emphasisButtonBackground) {
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button.emphasis { background: ${emphasisButtonBackground}; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer button.emphasis { background: ${emphasisButtonBackground}; }`);
}
const pendingItemColor = theme.getColor(buttonSecondaryBackground);
if (pendingItemColor) {
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .codicon { color: ${pendingItemColor} } `);
collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .codicon { color: ${pendingItemColor} } `);
}
const emphasisButtonHoverBackground = theme.getColor(buttonHoverBackground);
if (emphasisButtonHoverBackground) {
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button.emphasis:hover { background: ${emphasisButtonHoverBackground}; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer button.emphasis:hover { background: ${emphasisButtonHoverBackground}; }`);
}
const link = theme.getColor(textLinkForeground);
if (link) {
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer a { color: ${link}; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .button-link { color: ${link}; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .button-link * { color: ${link}; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer a { color: ${link}; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .button-link { color: ${link}; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .button-link * { color: ${link}; }`);
}
const activeLink = theme.getColor(textLinkActiveForeground);
if (activeLink) {
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer a:hover,
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer a:active { color: ${activeLink}; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer a:hover,
.monaco-workbench .part.editor > .content .gettingStartedContainer a:active { color: ${activeLink}; }`);
}
const focusColor = theme.getColor(focusBorder);
if (focusColor) {
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer a:focus { outline-color: ${focusColor}; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer a:focus { outline-color: ${focusColor}; }`);
}
const border = theme.getColor(contrastBorder);
if (border) {
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button { border-color: ${border}; border: 1px solid; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer button { border-color: ${border}; border: 1px solid; }`);
}
const activeBorder = theme.getColor(activeContrastBorder);
if (activeBorder) {
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button:hover { outline-color: ${activeBorder}; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer button:hover { outline-color: ${activeBorder}; }`);
}
const progressBackground = theme.getColor(welcomePageProgressBackground);
if (progressBackground) {
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .progress-bar-outer { background-color: ${progressBackground}; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .progress-bar-outer { background-color: ${progressBackground}; }`);
}
const progressForeground = theme.getColor(welcomePageProgressForeground);
if (progressForeground) {
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .progress-bar-inner { background-color: ${progressForeground}; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .progress-bar-inner { background-color: ${progressForeground}; }`);
}
});

View file

@ -1,36 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { escape } from 'vs/base/common/strings';
import { localize } from 'vs/nls';
export default () => `
<div class="gettingStartedContainer">
<div class="gettingStarted" role="document">
<div class="gettingStartedSlideCategory gettingStartedSlide categories">
<div class="gap"></div>
<div class="header">
<h1 class="product-name caption">${escape(localize('gettingStarted.vscode', "Visual Studio Code"))}</h1>
<p class="subtitle description">${escape(localize({ key: 'gettingStarted.editingRedefined', comment: ['Shown as subtitle on the Welcome page.'] }, "Code editing. Redefined"))}</p>
</div>
</div>
<div class="gettingStartedSlideDetails gettingStartedSlide detail">
<button class="prev-button button-link" x-dispatch="scrollPrev"><span class="scroll-button codicon codicon-chevron-left"></span>Back</a>
<div class="gettingStartedDetailsContent">
<div class="gap"></div>
<div class="getting-started-detail-columns">
<div class="gap"></div>
<div class="getting-started-detail-left">
<div class="getting-started-detail-title"></div>
</div>
<div class="getting-started-detail-right"></div>
<div class="gap"></div>
</div>
<div class="gap"></div>
</div>
</div>
</div>
</div>
`.replace(/\|/g, '`');

View file

@ -13,7 +13,6 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur
import { IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration';
import product from 'vs/platform/product/common/product';
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
.registerConfiguration({
@ -22,21 +21,14 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
'workbench.startupEditor': {
'scope': ConfigurationScope.RESOURCE,
'type': 'string',
'enum': [
...['none', 'welcomePage', 'readme', 'newUntitledFile', 'welcomePageInEmptyWorkbench'],
...(product.quality !== 'stable'
? ['gettingStarted']
: [])
],
'enum': ['none', 'welcomePage', 'readme', 'newUntitledFile', 'welcomePageInEmptyWorkbench', 'gettingStarted'],
'enumDescriptions': [...[
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.none' }, "Start without an editor."),
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePage' }, "Open the Welcome page (default)."),
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePage' }, "Open the Welcome page."),
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.readme' }, "Open the README when opening a folder that contains one, fallback to 'welcomePage' otherwise."),
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.newUntitledFile' }, "Open a new untitled file (only applies when opening an empty window)."),
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePageInEmptyWorkbench' }, "Open the Welcome page when opening an empty workbench."),],
...(product.quality !== 'stable'
? [localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.gettingStarted' }, "Open the Getting Started page (experimental).")]
: [])
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePageInEmptyWorkbench' }, "Open the Welcome page when opening an empty workbench."),
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.gettingStarted' }, "Open the Getting Started page.")]
],
'default': 'welcomePage',
'description': localize('workbench.startupEditor', "Controls which editor is shown at startup, if none are restored from the previous session.")

View file

@ -46,7 +46,7 @@ import { IProductService } from 'vs/platform/product/common/productService';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { getGettingStartedInput, gettingStartedInputTypeId } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted';
import { GettingStartedInput, gettingStartedInputTypeId } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted';
import { welcomeButtonBackground, welcomeButtonHoverBackground, welcomePageBackground } from 'vs/workbench/contrib/welcome/page/browser/welcomePageColors';
const configurationKey = 'workbench.startupEditor';
@ -123,7 +123,8 @@ export class WelcomePageContribution implements IWorkbenchContribution {
}
if (startupEditorTypeID === gettingStartedInputTypeId) {
editorService.openEditor(instantiationService.invokeFunction(getGettingStartedInput, {}), options);
const launchEditor = instantiationService.createInstance(GettingStartedInput, {});
return editorService.openEditor(launchEditor, options);
} else {
const launchEditor = instantiationService.createInstance(WelcomePage);
return launchEditor.openEditor(options);

View file

@ -10,7 +10,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { Severity } from 'vs/platform/notification/common/notification';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkspaceTrustService, WorkspaceTrustContext, WORKSPACE_TRUST_ENABLED, WORKSPACE_TRUST_URI, WorkspaceTrustState, WorkspaceTrustStateChangeEvent, workspaceTrustStateToString } from 'vs/platform/workspace/common/workspaceTrust';
import { IWorkspaceTrustService, WorkspaceTrustState, WorkspaceTrustStateChangeEvent, workspaceTrustStateToString } from 'vs/platform/workspace/common/workspaceTrust';
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import { IActivityService, IconBadge } from 'vs/workbench/services/activity/common/activity';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
@ -20,11 +20,11 @@ import { ThemeColor } from 'vs/workbench/api/common/extHostTypes';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { WorkspaceTrustFileSystemProvider } from 'vs/workbench/contrib/workspace/common/workspaceTrustFileSystemProvider';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar';
import { WorkspaceTrustContext, WORKSPACE_TRUST_ENABLED, WORKSPACE_TRUST_URI } from 'vs/workbench/services/workspaces/common/workspaceTrust';
const workspaceTrustIcon = registerIcon('workspace-trust-icon', Codicon.shield, localize('workspaceTrustIcon', "Icon for workspace trust badge."));
@ -54,7 +54,7 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben
if (visible) {
this.badgeDisposable.value = this.activityService.showGlobalActivity({
badge: new IconBadge(workspaceTrustIcon, () => localize('requestTrustIconText', "Some features require workspace trust.")),
priority: 0
priority: 10
});
}
}
@ -200,7 +200,7 @@ registerAction2(class extends Action2 {
menu: {
id: MenuId.GlobalActivity,
when: WorkspaceTrustContext.PendingRequest,
group: '7_trust',
group: '6_workspace_trust',
order: 10
},
});
@ -240,7 +240,7 @@ registerAction2(class extends Action2 {
menu: {
id: MenuId.GlobalActivity,
when: WorkspaceTrustContext.PendingRequest,
group: '7_trust',
group: '6_workspace_trust',
order: 20
},
});
@ -264,53 +264,19 @@ registerAction2(class extends Action2 {
}
});
// Reset Workspace Trust
registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.trust.reset',
title: {
original: 'Reset Workspace Trust',
value: localize('reset', "Reset Workspace Trust")
},
category: localize('workspacesCategory', "Workspaces"),
f1: true,
precondition: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('empty').negate(), WorkspaceTrustContext.TrustState.isEqualTo(WorkspaceTrustState.Unknown).negate())
});
}
async run(accessor: ServicesAccessor) {
const dialogService = accessor.get(IDialogService);
const workspaceTrustService = accessor.get(IWorkspaceTrustService);
const result = await dialogService.confirm({
message: localize('reset', "Reset Workspace Trust"),
detail: localize('confirmResetWorkspaceTrust', "Resetting workspace trust to the workspace will disable features that may pose a security risk if the contents of the workspace cannot be trusted. Are you sure you want to reset trust this workspace?"),
primaryButton: localize('yesGrant', 'Yes'),
secondaryButton: localize('noGrant', 'No')
});
if (result.confirmed) {
workspaceTrustService.resetWorkspaceTrust();
}
return;
}
});
// Manage Workspace Trust
registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.trust.manage',
title: {
original: 'Manage Trusted Workspaces',
value: localize('manageWorkspaceTrust', "Manage Trusted Workspaces")
original: 'Manage Workspace Trust',
value: localize('manageWorkspaceTrust', "Manage Workspace Trust")
},
category: localize('workspacesCategory', "Workspaces"),
menu: {
id: MenuId.GlobalActivity,
group: '7_trust',
group: '6_workspace_trust',
order: 40,
when: ContextKeyExpr.and(ContextKeyExpr.equals(`config.${WORKSPACE_TRUST_ENABLED}`, true), WorkspaceTrustContext.PendingRequest.negate())
},
@ -327,9 +293,9 @@ registerAction2(class extends Action2 {
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
command: {
id: 'workbench.trust.manage',
title: localize('manageWorkspaceTrustPending', "Manage Trusted Workspaces (1)"),
title: localize('manageWorkspaceTrustPending', "Manage Workspace Trust (1)"),
},
group: '7_trust',
group: '6_workspace_trust',
order: 40,
when: ContextKeyExpr.and(ContextKeyExpr.equals(`config.${WORKSPACE_TRUST_ENABLED}`, true), WorkspaceTrustContext.PendingRequest)
});

View file

@ -10,7 +10,7 @@ import { FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { VSBuffer } from 'vs/base/common/buffer';
import { WORKSPACE_TRUST_STORAGE_KEY } from 'vs/platform/workspace/common/workspaceTrust';
import { WORKSPACE_TRUST_STORAGE_KEY } from 'vs/workbench/services/workspaces/common/workspaceTrust';
const WORKSPACE_TRUST_SCHEMA = 'workspaceTrust';

View file

@ -192,7 +192,6 @@ const authenticationExtPoint = ExtensionsRegistry.registerExtensionPoint<Authent
export class AuthenticationService extends Disposable implements IAuthenticationService {
declare readonly _serviceBrand: undefined;
private _placeholderMenuItem: IDisposable | undefined;
private _noAccountsMenuItem: IDisposable | undefined;
private _signInRequestItems = new Map<string, SessionRequestInfo>();
private _sessionAccessRequestItems = new Map<string, { [extensionId: string]: { disposables: IDisposable[], possibleSessions: AuthenticationSession[] } }>();
private _accountBadgeDisposable = this._register(new MutableDisposable());
@ -278,29 +277,6 @@ export class AuthenticationService extends Disposable implements IAuthentication
return this._authenticationProviders.has(id);
}
private updateAccountsMenuItem(): void {
let hasSession = false;
this._authenticationProviders.forEach(async provider => {
hasSession = hasSession || provider.hasSessions();
});
if (hasSession && this._noAccountsMenuItem) {
this._noAccountsMenuItem.dispose();
this._noAccountsMenuItem = undefined;
}
if (!hasSession && !this._noAccountsMenuItem) {
this._noAccountsMenuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, {
group: '0_accounts',
command: {
id: 'noAccounts',
title: nls.localize('noAccounts', "You are not signed in to any accounts"),
precondition: ContextKeyExpr.false()
},
});
}
}
registerAuthenticationProvider(id: string, authenticationProvider: MainThreadAuthenticationProvider): void {
this._authenticationProviders.set(id, authenticationProvider);
this._onDidRegisterAuthenticationProvider.fire({ id, label: authenticationProvider.label });
@ -309,8 +285,6 @@ export class AuthenticationService extends Disposable implements IAuthentication
this._placeholderMenuItem.dispose();
this._placeholderMenuItem = undefined;
}
this.updateAccountsMenuItem();
}
unregisterAuthenticationProvider(id: string): void {
@ -319,7 +293,6 @@ export class AuthenticationService extends Disposable implements IAuthentication
provider.dispose();
this._authenticationProviders.delete(id);
this._onDidUnregisterAuthenticationProvider.fire({ id, label: provider.label });
this.updateAccountsMenuItem();
const accessRequests = this._sessionAccessRequestItems.get(id) || {};
Object.keys(accessRequests).forEach(extensionId => {
@ -343,7 +316,6 @@ export class AuthenticationService extends Disposable implements IAuthentication
if (provider) {
this._onDidChangeSessions.fire({ providerId: id, label: provider.label, event: event });
await provider.updateSessionItems(event);
this.updateAccountsMenuItem();
if (event.added) {
await this.updateNewSessionRequests(provider);

View file

@ -31,7 +31,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host';
import { mock } from 'vs/base/test/common/mock';
import { IExtensionBisectService } from 'vs/workbench/services/extensionManagement/browser/extensionBisect';
import { IWorkspaceTrustService } from 'vs/platform/workspace/common/workspaceTrust';
import { TestWorkspaceTrustService } from 'vs/platform/workspace/test/common/testWorkspaceTrust';
import { TestWorkspaceTrustService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService';
function createStorageService(instantiationService: TestInstantiationService): IStorageService {
let service = instantiationService.get(IStorageService);

View file

@ -16,7 +16,6 @@ import { IModeService } from 'vs/editor/common/services/modeService';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import * as nls from 'vs/nls';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@ -217,6 +216,10 @@ export class PreferencesService extends Disposable implements IPreferencesServic
private openSettings2(options?: ISettingsEditorOptions): Promise<IEditorPane> {
const input = this.settingsEditor2Input;
options = {
...options,
focusSearch: true
};
return this.editorService.openEditor(input, options ? SettingsEditorOptions.create(options) : undefined)
.then(() => this.editorGroupService.activeGroup.activeEditorPane!);
}
@ -269,11 +272,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
return this.openOrSwitchSettings2(ConfigurationTarget.WORKSPACE_FOLDER, folder, options, group);
}
switchSettings(target: ConfigurationTarget, resource: URI, jsonEditor?: boolean): Promise<void> {
if (!jsonEditor) {
return this.doOpenSettings2(target, resource).then(() => undefined);
}
switchSettings(target: ConfigurationTarget, resource: URI): Promise<void> {
const activeEditorPane = this.editorService.activeEditorPane;
if (activeEditorPane?.input instanceof PreferencesEditorInput) {
return this.doSwitchSettings(target, resource, activeEditorPane.input, activeEditorPane.group).then(() => undefined);
@ -336,7 +335,12 @@ export class PreferencesService extends Disposable implements IPreferencesServic
}
private openOrSwitchSettings2(configurationTarget: ConfigurationTarget, folderUri?: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise<IEditorPane | undefined> {
return this.doOpenSettings2(configurationTarget, folderUri, options, group);
const settingsOptions: ISettingsEditorOptions = {
...options,
target: configurationTarget,
folderUri
};
return this.openSettings2(settingsOptions);
}
private doOpenSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise<IEditorPane | undefined> {
@ -388,17 +392,6 @@ export class PreferencesService extends Disposable implements IPreferencesServic
return this.instantiationService.createInstance(Settings2EditorModel, this.getDefaultSettings(ConfigurationTarget.USER_LOCAL));
}
private doOpenSettings2(target: ConfigurationTarget, folderUri: URI | undefined, options?: IEditorOptions, group?: IEditorGroup): Promise<IEditorPane | undefined> {
const input = this.settingsEditor2Input;
const settingsOptions: ISettingsEditorOptions = {
...options,
target,
folderUri
};
return this.editorService.openEditor(input, SettingsEditorOptions.create(settingsOptions), group);
}
private async doSwitchSettings(target: ConfigurationTarget, resource: URI, input: PreferencesEditorInput, group: IEditorGroup, options?: ISettingsEditorOptions): Promise<IEditorPane> {
const settingsURI = await this.getEditableSettingsURI(target, resource);
if (!settingsURI) {

View file

@ -169,6 +169,7 @@ export interface ISettingsEditorOptions extends IEditorOptions {
key: string;
edit?: boolean;
};
focusSearch?: boolean;
}
export class SettingsEditorOptions extends EditorOptions implements ISettingsEditorOptions {
@ -180,6 +181,7 @@ export class SettingsEditorOptions extends EditorOptions implements ISettingsEdi
key: string;
edit?: boolean;
};
focusSearch?: boolean;
static create(settings: ISettingsEditorOptions): SettingsEditorOptions {
const options = new SettingsEditorOptions();
@ -190,6 +192,7 @@ export class SettingsEditorOptions extends EditorOptions implements ISettingsEdi
options.query = settings.query;
options.revealSetting = settings.revealSetting;
options.pinned = true;
options.focusSearch = settings.focusSearch;
return options;
}
@ -221,7 +224,7 @@ export interface IPreferencesService {
openRemoteSettings(): Promise<IEditorPane | undefined>;
openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise<IEditorPane | undefined>;
openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise<IEditorPane | undefined>;
switchSettings(target: ConfigurationTarget, resource: URI, jsonEditor?: boolean): Promise<void>;
switchSettings(target: ConfigurationTarget, resource: URI): Promise<void>;
openGlobalKeybindingSettings(textual: boolean, options?: IKeybindingsEditorOptions): Promise<void>;
openDefaultKeybindingsFile(): Promise<IEditorPane | undefined>;
getEditableSettingsURI(configurationTarget: ConfigurationTarget, resource?: URI): Promise<URI | null>;

View file

@ -475,12 +475,14 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
if (!languageName) {
return untitledName;
}
const extension = this.modeService.getExtensions(languageName)[0];
if (extension) {
if (!untitledName.endsWith(extension)) {
return untitledName + extension;
}
}
const filename = this.modeService.getFilenames(languageName)[0];
return filename || untitledName;
}

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