diff --git a/.eslintrc.json b/.eslintrc.json index 63fb6b80ac5..51e623e8f1b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -104,6 +104,7 @@ "restrictions": [ "assert", "sinon", + "sinon-test", "vs/nls", "**/vs/base/common/**", "**/vs/base/test/common/**" @@ -139,6 +140,7 @@ "restrictions": [ "assert", "sinon", + "sinon-test", "vs/nls", "**/vs/base/{common,browser}/**", "**/vs/base/test/{common,browser}/**" @@ -212,6 +214,7 @@ "restrictions": [ "assert", "sinon", + "sinon-test", "vs/nls", "**/vs/base/common/**", "**/vs/base/parts/*/common/**", @@ -225,6 +228,7 @@ "restrictions": [ "assert", "sinon", + "sinon-test", "vs/nls", "**/vs/base/{common,browser}/**", "**/vs/base/parts/*/{common,browser}/**", @@ -289,6 +293,7 @@ "restrictions": [ "assert", "sinon", + "sinon-test", "vs/nls", "**/vs/base/{common,browser}/**", "**/vs/base/parts/*/{common,browser}/**", @@ -311,6 +316,7 @@ "restrictions": [ "assert", "sinon", + "sinon-test", "vs/nls", "**/vs/base/common/**", "**/vs/platform/*/common/**", @@ -334,6 +340,7 @@ "restrictions": [ "assert", "sinon", + "sinon-test", "vs/nls", "**/vs/base/{common,browser}/**", "**/vs/platform/*/{common,browser}/**", @@ -357,6 +364,7 @@ "restrictions": [ "assert", "sinon", + "sinon-test", "vs/nls", "**/vs/base/common/**", "**/vs/platform/*/common/**", @@ -383,6 +391,7 @@ "restrictions": [ "assert", "sinon", + "sinon-test", "vs/nls", "**/vs/base/{common,browser}/**", "**/vs/platform/*/{common,browser}/**", @@ -397,6 +406,7 @@ "restrictions": [ "assert", "sinon", + "sinon-test", "vs/nls", "**/vs/base/{common,browser}/**", "**/vs/base/test/{common,browser}/**", @@ -921,6 +931,7 @@ "**/vs/**", "assert", "sinon", + "sinon-test", "crypto", "vscode" ] @@ -952,6 +963,7 @@ "**/vs/**", "assert", "sinon", + "sinon-test", "crypto", "xterm*" ] @@ -962,6 +974,7 @@ "**/vs/**", "assert", "sinon", + "sinon-test", "crypto", "xterm*" ] @@ -989,6 +1002,7 @@ "vscode-dts-cancellation": "warn", "vscode-dts-use-thenable": "warn", "vscode-dts-region-comments": "warn", + "vscode-dts-vscode-in-comments": "warn", "vscode-dts-provider-naming": [ "warn", { diff --git a/.github/workflows/english-please.yml b/.github/workflows/english-please.yml index a11a9a357c6..48e675a1804 100644 --- a/.github/workflows/english-please.yml +++ b/.github/workflows/english-please.yml @@ -7,19 +7,17 @@ on: jobs: main: runs-on: ubuntu-latest + if: contains(github.event.issue.labels.*.name, '*english-please') steps: - name: Checkout Actions - if: contains(github.event.issue.labels.*.name, '*english-please') uses: actions/checkout@v2 with: repository: "microsoft/vscode-github-triage-actions" ref: stable path: ./actions - name: Install Actions - if: contains(github.event.issue.labels.*.name, '*english-please') run: npm install --production --prefix ./actions - name: Run English Please - if: contains(github.event.issue.labels.*.name, '*english-please') uses: ./actions/english-please with: appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}} diff --git a/.github/workflows/rich-navigation.yml b/.github/workflows/rich-navigation.yml index 07a175d90bb..5802800ff88 100644 --- a/.github/workflows/rich-navigation.yml +++ b/.github/workflows/rich-navigation.yml @@ -33,5 +33,5 @@ jobs: with: languages: typescript repo-token: ${{ secrets.GITHUB_TOKEN }} - typescriptVersion: 0.6.0-next.8 + typescriptVersion: 0.6.0-dev.1 continue-on-error: true diff --git a/build/filters.js b/build/filters.js index 8c0b94dc3f8..376a77ac64a 100644 --- a/build/filters.js +++ b/build/filters.js @@ -51,7 +51,7 @@ module.exports.indentationFilter = [ '!test/monaco/out/**', '!test/smoke/out/**', '!extensions/typescript-language-features/test-workspace/**', - '!extensions/notebook-markdown-extensions/notebook-out/**', + '!extensions/markdown-math/notebook-out/**', '!extensions/vscode-api-tests/testWorkspace/**', '!extensions/vscode-api-tests/testWorkspace2/**', '!extensions/vscode-custom-editor-tests/test-workspace/**', @@ -89,7 +89,7 @@ module.exports.indentationFilter = [ '!**/*.dockerfile', '!extensions/markdown-language-features/media/*.js', '!extensions/markdown-language-features/notebook-out/*.js', - '!extensions/notebook-markdown-extensions/notebook-out/*.js', + '!extensions/markdown-math/notebook-out/*.js', '!extensions/simple-browser/media/*.js', ]; @@ -119,7 +119,7 @@ module.exports.copyrightFilter = [ '!resources/completions/**', '!extensions/configuration-editing/build/inline-allOf.ts', '!extensions/markdown-language-features/media/highlight.css', - '!extensions/notebook-markdown-extensions/notebook-out/**', + '!extensions/markdown-math/notebook-out/**', '!extensions/html-language-features/server/src/modes/typescript/*', '!extensions/*/server/bin/*', '!src/vs/editor/test/node/classification/typescript-test.ts', diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index cbb7c486b13..60e41d2b020 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -50,6 +50,7 @@ const compilations = [ 'json-language-features/server/tsconfig.json', 'markdown-language-features/preview-src/tsconfig.json', 'markdown-language-features/tsconfig.json', + 'markdown-math/tsconfig.json', 'merge-conflict/tsconfig.json', 'microsoft-authentication/tsconfig.json', 'npm/tsconfig.json', diff --git a/build/lib/eslint/vscode-dts-vscode-in-comments.js b/build/lib/eslint/vscode-dts-vscode-in-comments.js new file mode 100644 index 00000000000..8f9a13fb01f --- /dev/null +++ b/build/lib/eslint/vscode-dts-vscode-in-comments.js @@ -0,0 +1,45 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +module.exports = new class ApiVsCodeInComments { + constructor() { + this.meta = { + messages: { + comment: `Don't use the term 'vs code' in comments` + } + }; + } + create(context) { + const sourceCode = context.getSourceCode(); + return { + ['Program']: (_node) => { + for (const comment of sourceCode.getAllComments()) { + if (comment.type !== 'Block') { + continue; + } + if (!comment.range) { + continue; + } + const startIndex = comment.range[0] + '/*'.length; + const re = /vs code/ig; + let match; + while ((match = re.exec(comment.value))) { + // Allow using 'VS Code' in quotes + if (comment.value[match.index - 1] === `'` && comment.value[match.index + match[0].length] === `'`) { + continue; + } + // Types for eslint seem incorrect + const start = sourceCode.getLocFromIndex(startIndex + match.index); + const end = sourceCode.getLocFromIndex(startIndex + match.index + match[0].length); + context.report({ + messageId: 'comment', + loc: { start, end } + }); + } + } + } + }; + } +}; diff --git a/build/lib/eslint/vscode-dts-vscode-in-comments.ts b/build/lib/eslint/vscode-dts-vscode-in-comments.ts new file mode 100644 index 00000000000..8ced8eb25fd --- /dev/null +++ b/build/lib/eslint/vscode-dts-vscode-in-comments.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as eslint from 'eslint'; +import type * as estree from 'estree'; + +export = new class ApiVsCodeInComments implements eslint.Rule.RuleModule { + + readonly meta: eslint.Rule.RuleMetaData = { + messages: { + comment: `Don't use the term 'vs code' in comments` + } + }; + + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + + const sourceCode = context.getSourceCode(); + + return { + ['Program']: (_node: any) => { + + for (const comment of sourceCode.getAllComments()) { + if (comment.type !== 'Block') { + continue; + } + if (!comment.range) { + continue; + } + + const startIndex = comment.range[0] + '/*'.length; + const re = /vs code/ig; + let match: RegExpExecArray | null; + while ((match = re.exec(comment.value))) { + // Allow using 'VS Code' in quotes + if (comment.value[match.index - 1] === `'` && comment.value[match.index + match[0].length] === `'`) { + continue; + } + + // Types for eslint seem incorrect + const start = sourceCode.getLocFromIndex(startIndex + match.index) as any as estree.Position; + const end = sourceCode.getLocFromIndex(startIndex + match.index + match[0].length) as any as estree.Position; + context.report({ + messageId: 'comment', + loc: { start, end } + }); + } + } + } + }; + } +}; diff --git a/build/lib/extensions.js b/build/lib/extensions.js index 3d6a2906178..20ff7e1c486 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -338,7 +338,7 @@ const webpackMediaConfigFiles = [ // Additional projects to run esbuild on. These typically build code for webviews const esbuildMediaScripts = [ 'markdown-language-features/esbuild.js', - 'notebook-markdown-extensions/esbuild.js', + 'markdown-math/esbuild.js', ]; async function webpackExtensions(taskName, isWatch, webpackConfigLocations) { const webpack = require('webpack'); diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index 6c55569b973..19a53aee4b0 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -417,7 +417,7 @@ const webpackMediaConfigFiles = [ // Additional projects to run esbuild on. These typically build code for webviews const esbuildMediaScripts = [ 'markdown-language-features/esbuild.js', - 'notebook-markdown-extensions/esbuild.js', + 'markdown-math/esbuild.js', ]; export async function webpackExtensions(taskName: string, isWatch: boolean, webpackConfigLocations: { configPath: string, outputRoot?: string }[]) { diff --git a/build/lib/watch/yarn.lock b/build/lib/watch/yarn.lock index b0d7dd4a9f1..258c0edadad 100644 --- a/build/lib/watch/yarn.lock +++ b/build/lib/watch/yarn.lock @@ -148,9 +148,9 @@ fsevents@~2.3.1: integrity sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw== glob-parent@^5.1.1, glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" diff --git a/build/npm/dirs.js b/build/npm/dirs.js index ce069fbe29b..4e1b46179f5 100644 --- a/build/npm/dirs.js +++ b/build/npm/dirs.js @@ -28,9 +28,9 @@ exports.dirs = [ 'extensions/json-language-features', 'extensions/json-language-features/server', 'extensions/markdown-language-features', + 'extensions/markdown-math', 'extensions/merge-conflict', 'extensions/microsoft-authentication', - 'extensions/notebook-markdown-extensions', 'extensions/npm', 'extensions/php-language-features', 'extensions/search-result', diff --git a/build/npm/update-localization-extension.js b/build/npm/update-localization-extension.js index 43bc792d9f8..976776c6901 100644 --- a/build/npm/update-localization-extension.js +++ b/build/npm/update-localization-extension.js @@ -25,7 +25,7 @@ function update(options) { throw new Error(`${location} doesn't exist.`); } let locExtFolder = idOrPath; - if (/^\w{2}(-\w+)?$/.test(idOrPath)) { + if (/^\w{2,3}(-\w+)?$/.test(idOrPath)) { locExtFolder = path.join('..', 'vscode-loc', 'i18n', `vscode-language-pack-${idOrPath}`); } let locExtStat = fs.statSync(locExtFolder); @@ -88,7 +88,7 @@ function update(options) { for (let tp of translationPaths) { localization.translations.push({ id: tp.id, path: `./translations/${tp.resourceName}` }); } - fs.writeFileSync(path.join(locExtFolder, 'package.json'), JSON.stringify(packageJSON, null, '\t')); + fs.writeFileSync(path.join(locExtFolder, 'package.json'), JSON.stringify(packageJSON, null, '\t') + '\n'); } }); }); diff --git a/build/yarn.lock b/build/yarn.lock index f30b35e4830..ef2db1ff90c 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -1488,9 +1488,9 @@ node-fetch@^2.6.0: integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== normalize-url@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== + version "4.5.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== nth-check@~1.0.1: version "1.0.2" diff --git a/extensions/markdown-language-features/notebook/index.ts b/extensions/markdown-language-features/notebook/index.ts index 371091691b7..4dc45856e45 100644 --- a/extensions/markdown-language-features/notebook/index.ts +++ b/extensions/markdown-language-features/notebook/index.ts @@ -10,14 +10,164 @@ export function activate() { html: true }); + const style = document.createElement('style'); + style.classList.add('markdown-style'); + style.textContent = ` + .emptyMarkdownCell::before { + content: "${document.documentElement.style.getPropertyValue('--notebook-cell-markup-empty-content')}"; + font-style: italic; + opacity: 0.6; + } + + img { + max-width: 100%; + max-height: 100%; + } + + a { + text-decoration: none; + } + + a:hover { + text-decoration: underline; + } + + a:focus, + input:focus, + select:focus, + textarea:focus { + outline: 1px solid -webkit-focus-ring-color; + outline-offset: -1px; + } + + hr { + border: 0; + height: 2px; + border-bottom: 2px solid; + } + + h1 { + font-size: 26px; + line-height: 31px; + margin: 0; + margin-bottom: 13px; + } + + h2 { + font-size: 19px; + margin: 0; + margin-bottom: 10px; + } + + h1, + h2, + h3 { + font-weight: normal; + } + + div { + width: 100%; + } + + /* Adjust margin of first item in markdown cell */ + *:first-child { + margin-top: 0px; + } + + /* h1 tags don't need top margin */ + h1:first-child { + margin-top: 0; + } + + /* Removes bottom margin when only one item exists in markdown cell */ + *:only-child, + *:last-child { + margin-bottom: 0; + padding-bottom: 0; + } + + /* makes all markdown cells consistent */ + div { + min-height: var(--notebook-markdown-min-height); + } + + table { + border-collapse: collapse; + border-spacing: 0; + } + + table th, + table td { + border: 1px solid; + } + + table > thead > tr > th { + text-align: left; + border-bottom: 1px solid; + } + + table > thead > tr > th, + table > thead > tr > td, + table > tbody > tr > th, + table > tbody > tr > td { + padding: 5px 10px; + } + + table > tbody > tr + tr > td { + border-top: 1px solid; + } + + blockquote { + margin: 0 7px 0 5px; + padding: 0 16px 0 10px; + border-left-width: 5px; + border-left-style: solid; + } + + code, + .code { + font-size: 1em; + line-height: 1.357em; + } + + .code { + white-space: pre-wrap; + } + `; + document.head.append(style); + return { renderOutputItem: (outputInfo: { text(): string }, element: HTMLElement) => { - const rendered = markdownIt.render(outputInfo.text()); - element.innerHTML = rendered; + let previewNode: HTMLElement; + if (!element.shadowRoot) { + const previewRoot = element.attachShadow({ mode: 'open' }); - // Insert styles into markdown preview shadow dom so that they are applied - for (const markdownStyleNode of document.getElementsByClassName('markdown-style')) { - element.insertAdjacentElement('beforebegin', markdownStyleNode.cloneNode(true) as Element); + // Insert styles into markdown preview shadow dom so that they are applied. + // First add default webview style + const defaultStyles = document.getElementById('_defaultStyles') as HTMLStyleElement; + previewRoot.appendChild(defaultStyles.cloneNode(true)); + + // And then contributed styles + for (const markdownStyleNode of document.getElementsByClassName('markdown-style')) { + previewRoot.appendChild(markdownStyleNode.cloneNode(true)); + } + + previewNode = document.createElement('div'); + previewNode.id = 'preview'; + previewRoot.appendChild(previewNode); + } else { + previewNode = element.shadowRoot.getElementById('preview')!; + } + + const text = outputInfo.text(); + if (text.trim().length === 0) { + previewNode.innerText = ''; + previewNode.classList.add('emptyMarkdownCell'); + } else { + previewNode.classList.remove('emptyMarkdownCell'); + + const rendered = markdownIt.render(text); + previewNode.innerHTML = rendered; } }, extendMarkdownIt: (f: (md: typeof markdownIt) => void) => { diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index ec72a0d5344..61d2a848089 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -26,6 +26,7 @@ "onCommand:markdown.showSource", "onCommand:markdown.showPreviewSecuritySelector", "onCommand:markdown.api.render", + "onCommand:markdown.api.reloadPlugins", "onWebviewPanel:markdown.preview", "onCustomEditor:vscode.markdown.preview.editor" ], diff --git a/extensions/markdown-language-features/src/commands/index.ts b/extensions/markdown-language-features/src/commands/index.ts index 68aff7ffcf5..d810fab4931 100644 --- a/extensions/markdown-language-features/src/commands/index.ts +++ b/extensions/markdown-language-features/src/commands/index.ts @@ -3,11 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export { OpenDocumentLinkCommand } from './openDocumentLink'; -export { ShowPreviewCommand, ShowPreviewToSideCommand, ShowLockedPreviewToSideCommand } from './showPreview'; -export { ShowSourceCommand } from './showSource'; -export { RefreshPreviewCommand } from './refreshPreview'; -export { ShowPreviewSecuritySelectorCommand } from './showPreviewSecuritySelector'; export { MoveCursorToPositionCommand } from './moveCursorToPosition'; -export { ToggleLockCommand } from './toggleLock'; +export { OpenDocumentLinkCommand } from './openDocumentLink'; +export { RefreshPreviewCommand } from './refreshPreview'; +export { ReloadPlugins } from './reloadPlugins'; export { RenderDocument } from './renderDocument'; +export { ShowLockedPreviewToSideCommand, ShowPreviewCommand, ShowPreviewToSideCommand } from './showPreview'; +export { ShowPreviewSecuritySelectorCommand } from './showPreviewSecuritySelector'; +export { ShowSourceCommand } from './showSource'; +export { ToggleLockCommand } from './toggleLock'; + diff --git a/extensions/markdown-language-features/src/commands/reloadPlugins.ts b/extensions/markdown-language-features/src/commands/reloadPlugins.ts new file mode 100644 index 00000000000..f712a41d438 --- /dev/null +++ b/extensions/markdown-language-features/src/commands/reloadPlugins.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Command } from '../commandManager'; +import { MarkdownPreviewManager } from '../features/previewManager'; +import { MarkdownEngine } from '../markdownEngine'; + +export class ReloadPlugins implements Command { + public readonly id = 'markdown.api.reloadPlugins'; + + public constructor( + private readonly webviewManager: MarkdownPreviewManager, + private readonly engine: MarkdownEngine, + ) { } + + public execute(): void { + this.engine.reloadPlugins(); + this.engine.cleanCache(); + this.webviewManager.refresh(); + } +} diff --git a/extensions/markdown-language-features/src/extension.ts b/extensions/markdown-language-features/src/extension.ts index a3479c6cd77..030015aa055 100644 --- a/extensions/markdown-language-features/src/extension.ts +++ b/extensions/markdown-language-features/src/extension.ts @@ -80,6 +80,7 @@ function registerMarkdownCommands( commandManager.register(new commands.OpenDocumentLinkCommand(engine)); commandManager.register(new commands.ToggleLockCommand(previewManager)); commandManager.register(new commands.RenderDocument(engine)); + commandManager.register(new commands.ReloadPlugins(previewManager, engine)); return commandManager; } diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts index 346547f3da9..83d7e82da95 100644 --- a/extensions/markdown-language-features/src/markdownEngine.ts +++ b/extensions/markdown-language-features/src/markdownEngine.ts @@ -67,6 +67,7 @@ interface RenderEnv { } export class MarkdownEngine { + private md?: Promise; private _slugCount = new Map(); @@ -129,6 +130,10 @@ export class MarkdownEngine { return md; } + public reloadPlugins() { + this.md = undefined; + } + private tokenizeDocument( document: SkinnyTextDocument, config: MarkdownItConfig, diff --git a/extensions/notebook-markdown-extensions/.gitignore b/extensions/markdown-math/.gitignore similarity index 100% rename from extensions/notebook-markdown-extensions/.gitignore rename to extensions/markdown-math/.gitignore diff --git a/extensions/notebook-markdown-extensions/.vscodeignore b/extensions/markdown-math/.vscodeignore similarity index 100% rename from extensions/notebook-markdown-extensions/.vscodeignore rename to extensions/markdown-math/.vscodeignore diff --git a/extensions/notebook-markdown-extensions/README.md b/extensions/markdown-math/README.md similarity index 75% rename from extensions/notebook-markdown-extensions/README.md rename to extensions/markdown-math/README.md index cc1d7f6fc4f..64d18d01be3 100644 --- a/extensions/notebook-markdown-extensions/README.md +++ b/extensions/markdown-math/README.md @@ -1,3 +1,3 @@ -# Markdown Notebook Math support +# Markdown Math support **Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled. diff --git a/extensions/notebook-markdown-extensions/esbuild.js b/extensions/markdown-math/esbuild.js similarity index 100% rename from extensions/notebook-markdown-extensions/esbuild.js rename to extensions/markdown-math/esbuild.js diff --git a/extensions/markdown-math/extension-browser.webpack.config.js b/extensions/markdown-math/extension-browser.webpack.config.js new file mode 100644 index 00000000000..7fcc53a728b --- /dev/null +++ b/extensions/markdown-math/extension-browser.webpack.config.js @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withBrowserDefaults = require('../shared.webpack.config').browser; + +module.exports = withBrowserDefaults({ + context: __dirname, + entry: { + extension: './src/extension.ts' + } +}); diff --git a/extensions/markdown-math/extension.webpack.config.js b/extensions/markdown-math/extension.webpack.config.js new file mode 100644 index 00000000000..de88398eca0 --- /dev/null +++ b/extensions/markdown-math/extension.webpack.config.js @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withDefaults = require('../shared.webpack.config'); + +module.exports = withDefaults({ + context: __dirname, + resolve: { + mainFields: ['module', 'main'] + }, + entry: { + extension: './src/extension.ts', + } +}); diff --git a/extensions/notebook-markdown-extensions/icon.png b/extensions/markdown-math/icon.png similarity index 100% rename from extensions/notebook-markdown-extensions/icon.png rename to extensions/markdown-math/icon.png diff --git a/extensions/notebook-markdown-extensions/notebook/katex.ts b/extensions/markdown-math/notebook/katex.ts similarity index 100% rename from extensions/notebook-markdown-extensions/notebook/katex.ts rename to extensions/markdown-math/notebook/katex.ts diff --git a/extensions/notebook-markdown-extensions/notebook/tsconfig.json b/extensions/markdown-math/notebook/tsconfig.json similarity index 100% rename from extensions/notebook-markdown-extensions/notebook/tsconfig.json rename to extensions/markdown-math/notebook/tsconfig.json diff --git a/extensions/notebook-markdown-extensions/package.json b/extensions/markdown-math/package.json similarity index 58% rename from extensions/notebook-markdown-extensions/package.json rename to extensions/markdown-math/package.json index c708959a860..98c1c6e4552 100644 --- a/extensions/notebook-markdown-extensions/package.json +++ b/extensions/markdown-math/package.json @@ -1,11 +1,10 @@ { - "name": "notebook-markdown-extensions", + "name": "markdown-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": { @@ -20,16 +19,36 @@ "supported": true } }, + "main": "./out/extension", + "browser": "./dist/browser/extension", + "activationEvents": [], "contributes": { "notebookRenderer": [ { "id": "markdownItRenderer-katex", - "displayName": "Markdown it katex renderer", + "displayName": "Markdown it KaTeX renderer", "entrypoint": { "extends": "markdownItRenderer", "path": "./notebook-out/katex.js" } } + ], + "markdown.markdownItPlugins": true, + "markdown.previewStyles": [ + "./node_modules/katex/dist/katex.min.css", + "./preview-styles/index.css" + ], + "configuration": [ + { + "title": "Markdown", + "properties": { + "markdown.math.enabled": { + "type": "boolean", + "default": true, + "description": "%config.markdown.math.enabled%" + } + } + } ] }, "scripts": { @@ -37,10 +56,11 @@ "watch": "npm run build-notebook", "build-notebook": "node ./esbuild" }, + "dependencies": { + "@iktakahiro/markdown-it-katex": "https://github.com/mjbvz/markdown-it-katex.git" + }, "devDependencies": { - "@iktakahiro/markdown-it-katex": "https://github.com/mjbvz/markdown-it-katex.git", - "@types/markdown-it": "^0.0.0", - "markdown-it": "^12.0.4" + "@types/markdown-it": "^0.0.0" }, "repository": { "type": "git", diff --git a/extensions/markdown-math/package.nls.json b/extensions/markdown-math/package.nls.json new file mode 100644 index 00000000000..5fb6a52005a --- /dev/null +++ b/extensions/markdown-math/package.nls.json @@ -0,0 +1,5 @@ +{ + "displayName": "Markdown Math", + "description": "Adds math support to markdown in notebooks.", + "config.markdown.math.enabled": "Enable/disable rendering math in the built-in markdown preview." +} diff --git a/extensions/markdown-math/preview-styles/index.css b/extensions/markdown-math/preview-styles/index.css new file mode 100644 index 00000000000..183ac334790 --- /dev/null +++ b/extensions/markdown-math/preview-styles/index.css @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.katex-error { + color: var(--vscode-editorError-foreground); +} diff --git a/extensions/markdown-math/src/extension.ts b/extensions/markdown-math/src/extension.ts new file mode 100644 index 00000000000..196964dd459 --- /dev/null +++ b/extensions/markdown-math/src/extension.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; + +const enabledSetting = 'markdown.math.enabled'; + +export function activate(context: vscode.ExtensionContext) { + function isEnabled(): boolean { + const config = vscode.workspace.getConfiguration('markdown'); + console.log(config.get('math.enabled', true)); + return config.get('math.enabled', true); + } + + vscode.workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(enabledSetting)) { + vscode.commands.executeCommand('markdown.api.reloadPlugins'); + } + }, undefined, context.subscriptions); + + return { + extendMarkdownIt(md: any) { + if (isEnabled()) { + const katex = require('@iktakahiro/markdown-it-katex'); + return md.use(katex); + } + return md; + } + }; +} diff --git a/extensions/markdown-math/src/types.d.ts b/extensions/markdown-math/src/types.d.ts new file mode 100644 index 00000000000..711cf9c84be --- /dev/null +++ b/extensions/markdown-math/src/types.d.ts @@ -0,0 +1 @@ +/// diff --git a/extensions/markdown-math/tsconfig.json b/extensions/markdown-math/tsconfig.json new file mode 100644 index 00000000000..1decd91e333 --- /dev/null +++ b/extensions/markdown-math/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "outDir": "./out", + "experimentalDecorators": true, + "lib": [ + "es6", + "es2015.promise", + "es2019.array", + "es2020.string", + "dom" + ] + }, + "include": [ + "src/**/*" + ] +} diff --git a/extensions/markdown-math/yarn.lock b/extensions/markdown-math/yarn.lock new file mode 100644 index 00000000000..f402ca890b7 --- /dev/null +++ b/extensions/markdown-math/yarn.lock @@ -0,0 +1,26 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@iktakahiro/markdown-it-katex@https://github.com/mjbvz/markdown-it-katex.git": + version "4.0.1" + resolved "https://github.com/mjbvz/markdown-it-katex.git#2bf0b89c6c22ef0b585f55ccab66d1f7c5356bea" + dependencies: + katex "^0.13.0" + +"@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= + +commander@^6.0.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + +katex@^0.13.0: + version "0.13.0" + resolved "https://registry.yarnpkg.com/katex/-/katex-0.13.0.tgz#62900e56c1ad8fdf7da23399e50d7a7b690b39ab" + integrity sha512-6cHbzbegYgS9vvVGuH8UA+o97X+ZshtboSqJJCdq7trBYzuD75JNwr7Ef606xkUjecPPhFnyB+afx1dVafielg== + dependencies: + commander "^6.0.0" diff --git a/extensions/notebook-markdown-extensions/package.nls.json b/extensions/notebook-markdown-extensions/package.nls.json deleted file mode 100644 index e9ae9594dc1..00000000000 --- a/extensions/notebook-markdown-extensions/package.nls.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "displayName": "Markdown Notebook math", - "description": "Provides rich language support for Markdown." -} diff --git a/extensions/notebook-markdown-extensions/yarn.lock b/extensions/notebook-markdown-extensions/yarn.lock deleted file mode 100644 index 6e45bbe0e7b..00000000000 --- a/extensions/notebook-markdown-extensions/yarn.lock +++ /dev/null @@ -1,64 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@iktakahiro/markdown-it-katex@https://github.com/mjbvz/markdown-it-katex.git": - version "4.0.1" - resolved "https://github.com/mjbvz/markdown-it-katex.git#2bf0b89c6c22ef0b585f55ccab66d1f7c5356bea" - dependencies: - katex "^0.13.0" - -"@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= - -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== - -commander@^6.0.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" - integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== - -entities@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" - integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== - -katex@^0.13.0: - version "0.13.0" - resolved "https://registry.yarnpkg.com/katex/-/katex-0.13.0.tgz#62900e56c1ad8fdf7da23399e50d7a7b690b39ab" - integrity sha512-6cHbzbegYgS9vvVGuH8UA+o97X+ZshtboSqJJCdq7trBYzuD75JNwr7Ef606xkUjecPPhFnyB+afx1dVafielg== - dependencies: - commander "^6.0.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" - -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= - -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== diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 4990b4ce1c7..279d0e2d4d8 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -703,7 +703,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType if (isWeb()) { // On web, treat absolute paths as pointing to standard lib files if (filepath.startsWith('/')) { - return vscode.Uri.joinPath(this.context.extensionUri, 'node_modules', 'typescript', 'lib', filepath.slice(1)); + return vscode.Uri.joinPath(this.context.extensionUri, 'dist', 'browser', 'typescript', filepath.slice(1)); } } diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts index c4b290e984b..646f6214f98 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts @@ -21,6 +21,7 @@ suite('Notebook Editor', function () { }; const disposables: vscode.Disposable[] = []; + const testDisposables: vscode.Disposable[] = []; suiteTeardown(async function () { utils.assertNoRpc(); @@ -38,6 +39,10 @@ suite('Notebook Editor', function () { disposables.push(vscode.workspace.registerNotebookSerializer('notebook.nbdtest', contentSerializer)); }); + teardown(async function () { + utils.disposeAll(testDisposables); + testDisposables.length = 0; + }); test('showNotebookDocment', async function () { @@ -74,4 +79,48 @@ suite('Notebook Editor', function () { assert.ok(await openedEditor); assert.strictEqual(editor.document.uri.toString(), resource.toString()); }); + + test('Active/Visible Editor', async function () { + const firstEditorOpen = utils.asPromise(vscode.window.onDidChangeActiveNotebookEditor); + const resource = await utils.createRandomFile(undefined, undefined, '.nbdtest'); + const firstEditor = await vscode.window.showNotebookDocument(resource); + await firstEditorOpen; + assert.strictEqual(vscode.window.activeNotebookEditor, firstEditor); + assert.strictEqual(vscode.window.visibleNotebookEditors.includes(firstEditor), true); + + const secondEditor = await vscode.window.showNotebookDocument(resource, { viewColumn: vscode.ViewColumn.Beside }); + assert.strictEqual(secondEditor === vscode.window.activeNotebookEditor, true); + assert.notStrictEqual(firstEditor, secondEditor); + assert.strictEqual(vscode.window.visibleNotebookEditors.includes(secondEditor), true); + assert.strictEqual(vscode.window.visibleNotebookEditors.includes(firstEditor), true); + assert.strictEqual(vscode.window.visibleNotebookEditors.length, 2); + }); + + test('Notebook Editor Event - onDidChangeVisibleNotebookEditors on open/close', async function () { + const openedEditor = utils.asPromise(vscode.window.onDidChangeVisibleNotebookEditors); + const resource = await utils.createRandomFile(undefined, undefined, '.nbdtest'); + await vscode.window.showNotebookDocument(resource); + assert.ok(await openedEditor); + + const firstEditorClose = utils.asPromise(vscode.window.onDidChangeVisibleNotebookEditors); + await utils.closeAllEditors(); + await firstEditorClose; + }); + + test('Notebook Editor Event - onDidChangeVisibleNotebookEditors on two editor groups', async function () { + const resource = await utils.createRandomFile(undefined, undefined, '.nbdtest'); + let count = 0; + testDisposables.push(vscode.window.onDidChangeVisibleNotebookEditors(() => { + count = vscode.window.visibleNotebookEditors.length; + })); + + await vscode.window.showNotebookDocument(resource, { viewColumn: vscode.ViewColumn.Active }); + assert.strictEqual(count, 1); + + await vscode.window.showNotebookDocument(resource, { viewColumn: vscode.ViewColumn.Beside }); + assert.strictEqual(count, 2); + + await utils.closeAllEditors(); + assert.strictEqual(count, 0); + }); }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts index df8d73783e6..2ea186b0211 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts @@ -56,18 +56,13 @@ class Kernel { } protected async _runCell(cell: vscode.NotebookCell) { + // create a single output with exec order 1 and output is plain/text + // of either the cell itself or (iff empty) the cell's document's uri const task = this.controller.createNotebookCellExecution(cell); task.start(); task.executionOrder = 1; - if (cell.notebook.uri.path.endsWith('customRenderer.vsctestnb')) { - await task.replaceOutput([new vscode.NotebookCellOutput([ - vscode.NotebookCellOutputItem.text('test', 'text/custom') - ])]); - return; - } - await task.replaceOutput([new vscode.NotebookCellOutput([ - vscode.NotebookCellOutputItem.text('my output', 'text/plain') + vscode.NotebookCellOutputItem.text(cell.document.getText() || cell.document.uri.toString(), 'text/plain') ])]); task.end(true); } @@ -161,40 +156,12 @@ suite('Notebook API tests', function () { suiteDisposables.push(vscode.workspace.registerNotebookContentProvider('notebookCoreTest', apiTestContentProvider)); }); - let kernel1: Kernel; - let kernel2: Kernel; + let defaultKernel: Kernel; setup(async function () { - - kernel1 = new Kernel('mainKernel', 'Notebook Primary Test Kernel'); - - const listener = vscode.workspace.onDidOpenNotebookDocument(async notebook => { - if (notebook.notebookType === kernel1.controller.notebookType) { - await vscode.commands.executeCommand('notebook.selectKernel', { - extension: 'vscode.vscode-api-tests', - id: kernel1.controller.id - }); - } - }); - - - kernel2 = new class extends Kernel { - constructor() { - super('secondaryKernel', 'Notebook Secondary Test Kernel'); - this.controller.supportsExecutionOrder = false; - } - - override async _runCell(cell: vscode.NotebookCell) { - const task = this.controller.createNotebookCellExecution(cell); - task.start(); - await task.replaceOutput([new vscode.NotebookCellOutput([ - vscode.NotebookCellOutputItem.text('my second output', 'text/plain') - ])]); - task.end(true); - } - }; - - testDisposables.push(kernel1.controller, listener, kernel2.controller); + // there should be ONE default kernel in this suite + defaultKernel = new Kernel('mainKernel', 'Notebook Default Kernel'); + testDisposables.push(defaultKernel.controller); await saveAllFilesAndCloseAll(); }); @@ -204,35 +171,6 @@ suite('Notebook API tests', function () { await saveAllFilesAndCloseAll(); }); - test('editor onDidChangeVisibleNotebookEditors-event', async function () { - const resource = await createRandomNotebookFile(); - const firstEditorOpen = asPromise(vscode.window.onDidChangeVisibleNotebookEditors); - await vscode.window.showNotebookDocument(resource); - await firstEditorOpen; - - const firstEditorClose = asPromise(vscode.window.onDidChangeVisibleNotebookEditors); - await closeAllEditors(); - await firstEditorClose; - }); - - test('editor onDidChangeVisibleNotebookEditors-event 2', async function () { - const resource = await createRandomNotebookFile(); - let count = 0; - const disposables: vscode.Disposable[] = []; - disposables.push(vscode.window.onDidChangeVisibleNotebookEditors(() => { - count = vscode.window.visibleNotebookEditors.length; - })); - - await vscode.window.showNotebookDocument(resource, { viewColumn: vscode.ViewColumn.Active }); - assert.strictEqual(count, 1); - - await vscode.window.showNotebookDocument(resource, { viewColumn: vscode.ViewColumn.Beside }); - assert.strictEqual(count, 2); - - await closeAllEditors(); - assert.strictEqual(count, 0); - }); - test('correct cell selection on undo/redo of cell creation', async function () { const notebook = await openRandomNotebookDocument(); await vscode.window.showNotebookDocument(notebook); @@ -252,23 +190,21 @@ suite('Notebook API tests', function () { assert.strictEqual(selectionRedo[0].end, 2); }); - test('editor editing event 2', async function () { - // const notebook = await openRandomNotebookDocument(); - // await vscode.window.showNotebookDocument(notebook); - const resource = await createRandomNotebookFile(); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + test('editor editing event', async function () { + const notebook = await openRandomNotebookDocument(); + const editor = await vscode.window.showNotebookDocument(notebook); const cellsChangeEvent = asPromise(vscode.notebooks.onDidChangeNotebookCells); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); const cellChangeEventRet = await cellsChangeEvent; - assert.strictEqual(cellChangeEventRet.document, vscode.window.activeNotebookEditor?.document); + assert.strictEqual(cellChangeEventRet.document, editor.document); assert.strictEqual(cellChangeEventRet.changes.length, 1); assert.deepStrictEqual(cellChangeEventRet.changes[0], { start: 1, deletedCount: 0, deletedItems: [], items: [ - vscode.window.activeNotebookEditor!.document.cellAt(1) + editor.document.cellAt(1) ] }); @@ -280,8 +216,8 @@ suite('Notebook API tests', function () { await vscode.commands.executeCommand('notebook.cell.execute'); const cellOutputsAddedRet = await cellOutputChange; assert.deepStrictEqual(cellOutputsAddedRet, { - document: vscode.window.activeNotebookEditor!.document, - cells: [vscode.window.activeNotebookEditor!.document.cellAt(0)] + document: editor.document, + cells: [editor.document.cellAt(0)] }); assert.strictEqual(cellOutputsAddedRet.cells[0].outputs.length, 1); @@ -289,8 +225,8 @@ suite('Notebook API tests', function () { await vscode.commands.executeCommand('notebook.cell.clearOutputs'); const cellOutputsCleardRet = await cellOutputClear; assert.deepStrictEqual(cellOutputsCleardRet, { - document: vscode.window.activeNotebookEditor!.document, - cells: [vscode.window.activeNotebookEditor!.document.cellAt(0)] + document: editor.document, + cells: [editor.document.cellAt(0)] }); assert.strictEqual(cellOutputsAddedRet.cells[0].outputs.length, 0); @@ -304,111 +240,45 @@ suite('Notebook API tests', function () { // }); }); - test('editor move cell event', async function () { - const resource = await createRandomNotebookFile(); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - // const notebook = await openRandomNotebookDocument(); - // await vscode.window.showNotebookDocument(notebook); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - await vscode.commands.executeCommand('notebook.focusTop'); - - const activeCell = getFocusedCell(vscode.window.activeNotebookEditor); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 0); - const moveChange = asPromise(vscode.notebooks.onDidChangeNotebookCells); - await vscode.commands.executeCommand('notebook.cell.moveDown'); - await moveChange; - await saveAllEditors(); - await closeAllEditors(); - - // await vscode.window.showNotebookDocument(notebook); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - const firstEditor = vscode.window.activeNotebookEditor; - assert.strictEqual(firstEditor?.document.cellCount, 2); - }); - - test('notebook editor active/visible', async function () { - const resource = await createRandomNotebookFile(); - const firstEditor = await vscode.window.showNotebookDocument(resource, { viewColumn: vscode.ViewColumn.Active }); - assert.strictEqual(firstEditor === vscode.window.activeNotebookEditor, true); - assert.strictEqual(vscode.window.visibleNotebookEditors.includes(firstEditor), true); - - const secondEditor = await vscode.window.showNotebookDocument(resource, { viewColumn: vscode.ViewColumn.Beside }); - assert.strictEqual(secondEditor === vscode.window.activeNotebookEditor, true); - - assert.notStrictEqual(firstEditor, secondEditor); - assert.strictEqual(vscode.window.visibleNotebookEditors.includes(secondEditor), true); - assert.strictEqual(vscode.window.visibleNotebookEditors.includes(firstEditor), true); - assert.strictEqual(vscode.window.visibleNotebookEditors.length, 2); - - const untitledEditorChange = asPromise(vscode.window.onDidChangeActiveNotebookEditor); - await vscode.commands.executeCommand('workbench.action.files.newUntitledFile'); - await untitledEditorChange; - assert.strictEqual(firstEditor && vscode.window.visibleNotebookEditors.indexOf(firstEditor) >= 0, true); - assert.notStrictEqual(firstEditor, vscode.window.activeNotebookEditor); - assert.strictEqual(secondEditor && vscode.window.visibleNotebookEditors.indexOf(secondEditor) < 0, true); - assert.notStrictEqual(secondEditor, vscode.window.activeNotebookEditor); - assert.strictEqual(vscode.window.visibleNotebookEditors.length, 1); - - const activeEditorClose = asPromise(vscode.window.onDidChangeActiveNotebookEditor); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - await activeEditorClose; - assert.strictEqual(secondEditor, vscode.window.activeNotebookEditor); - assert.strictEqual(vscode.window.visibleNotebookEditors.length, 2); - assert.strictEqual(secondEditor && vscode.window.visibleNotebookEditors.indexOf(secondEditor) >= 0, true); - }); - - test('notebook active editor change', async function () { - const firstEditorOpen = asPromise(vscode.window.onDidChangeActiveNotebookEditor); - const notebook = await openRandomNotebookDocument(); - await vscode.window.showNotebookDocument(notebook); - await firstEditorOpen; - - const firstEditorDeactivate = asPromise(vscode.window.onDidChangeActiveNotebookEditor); - await vscode.commands.executeCommand('workbench.action.splitEditor'); - await firstEditorDeactivate; - }); - test('edit API batch edits', async function () { - const resource = await createRandomNotebookFile(); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + const notebook = await openRandomNotebookDocument(); + const editor = await vscode.window.showNotebookDocument(notebook); const cellsChangeEvent = asPromise(vscode.notebooks.onDidChangeNotebookCells); const cellMetadataChangeEvent = asPromise(vscode.notebooks.onDidChangeCellMetadata); - const version = vscode.window.activeNotebookEditor!.document.version; - await vscode.window.activeNotebookEditor!.edit(editBuilder => { + const version = editor.document.version; + await editor.edit(editBuilder => { editBuilder.replaceCells(1, 0, [{ kind: vscode.NotebookCellKind.Code, languageId: 'javascript', value: 'test 2', outputs: [], metadata: undefined }]); editBuilder.replaceCellMetadata(0, { inputCollapsed: false }); }); await cellsChangeEvent; await cellMetadataChangeEvent; - assert.strictEqual(version + 1, vscode.window.activeNotebookEditor!.document.version); + assert.strictEqual(version + 1, editor.document.version); }); test('edit API batch edits undo/redo', async function () { const notebook = await openRandomNotebookDocument(); - await vscode.window.showNotebookDocument(notebook); + const editor = await vscode.window.showNotebookDocument(notebook); const cellsChangeEvent = asPromise(vscode.notebooks.onDidChangeNotebookCells); const cellMetadataChangeEvent = asPromise(vscode.notebooks.onDidChangeCellMetadata); - const version = vscode.window.activeNotebookEditor!.document.version; - await vscode.window.activeNotebookEditor!.edit(editBuilder => { + const version = editor.document.version; + await editor.edit(editBuilder => { editBuilder.replaceCells(1, 0, [{ kind: vscode.NotebookCellKind.Code, languageId: 'javascript', value: 'test 2', outputs: [], metadata: undefined }]); editBuilder.replaceCellMetadata(0, { inputCollapsed: false }); }); await cellsChangeEvent; await cellMetadataChangeEvent; - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellCount, 3); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellAt(0)?.metadata.inputCollapsed, false); - assert.strictEqual(version + 1, vscode.window.activeNotebookEditor!.document.version); + assert.strictEqual(editor.document.cellCount, 3); + assert.strictEqual(editor.document.cellAt(0)?.metadata.inputCollapsed, false); + assert.strictEqual(version + 1, editor.document.version); await vscode.commands.executeCommand('undo'); - assert.strictEqual(version + 2, vscode.window.activeNotebookEditor!.document.version); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellAt(0)?.metadata.inputCollapsed, undefined); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellCount, 2); + assert.strictEqual(version + 2, editor.document.version); + assert.strictEqual(editor.document.cellAt(0)?.metadata.inputCollapsed, undefined); + assert.strictEqual(editor.document.cellCount, 2); }); test('#98841, initialzation should not emit cell change events.', async function () { @@ -422,76 +292,61 @@ suite('Notebook API tests', function () { await vscode.window.showNotebookDocument(notebook); assert.strictEqual(count, 0); }); - // }); - - // suite('notebook workflow', () => { test('notebook open', async function () { const notebook = await openRandomNotebookDocument(); - await vscode.window.showNotebookDocument(notebook); - assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'test'); - assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.languageId, 'typescript'); + const editor = await vscode.window.showNotebookDocument(notebook); + assert.strictEqual(vscode.window.activeNotebookEditor === editor, true, 'notebook first'); + assert.strictEqual(getFocusedCell(editor)?.document.getText(), 'test'); + assert.strictEqual(getFocusedCell(editor)?.document.languageId, 'typescript'); - const secondCell = vscode.window.activeNotebookEditor!.document.cellAt(1); - assert.strictEqual(secondCell!.outputs.length, 1); - assert.deepStrictEqual(secondCell!.outputs[0].metadata, { testOutputMetadata: true, ['text/plain']: { testOutputItemMetadata: true } }); - assert.strictEqual(secondCell!.outputs[0].items.length, 1); - assert.strictEqual(secondCell!.outputs[0].items[0].mime, 'text/plain'); - assert.strictEqual(new TextDecoder().decode(secondCell!.outputs[0].items[0].data), 'Hello World'); - assert.strictEqual(secondCell!.executionSummary?.executionOrder, 5); - assert.strictEqual(secondCell!.executionSummary?.success, true); - - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), ''); - - await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - const activeCell = getFocusedCell(vscode.window.activeNotebookEditor); - assert.notStrictEqual(getFocusedCell(vscode.window.activeNotebookEditor), undefined); - assert.strictEqual(activeCell!.document.getText(), ''); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellCount, 4); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 1); - - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + const secondCell = editor.document.cellAt(1); + assert.strictEqual(secondCell.outputs.length, 1); + assert.deepStrictEqual(secondCell.outputs[0].metadata, { testOutputMetadata: true, ['text/plain']: { testOutputItemMetadata: true } }); + assert.strictEqual(secondCell.outputs[0].items.length, 1); + assert.strictEqual(secondCell.outputs[0].items[0].mime, 'text/plain'); + assert.strictEqual(new TextDecoder().decode(secondCell.outputs[0].items[0].data), 'Hello World'); + assert.strictEqual(secondCell.executionSummary?.executionOrder, 5); + assert.strictEqual(secondCell.executionSummary?.success, true); }); test('notebook cell actions', async function () { const notebook = await openRandomNotebookDocument(); - await vscode.window.showNotebookDocument(notebook); + const editor = await vscode.window.showNotebookDocument(notebook); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'test'); - assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.languageId, 'typescript'); + assert.strictEqual(vscode.window.activeNotebookEditor === editor, true, 'notebook first'); + assert.strictEqual(getFocusedCell(editor)?.document.getText(), 'test'); + assert.strictEqual(getFocusedCell(editor)?.document.languageId, 'typescript'); // ---- insert cell below and focus ---- // await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), ''); + assert.strictEqual(getFocusedCell(editor)?.document.getText(), ''); // ---- insert cell above and focus ---- // await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - let activeCell = getFocusedCell(vscode.window.activeNotebookEditor); - assert.notStrictEqual(getFocusedCell(vscode.window.activeNotebookEditor), undefined); + let activeCell = getFocusedCell(editor); + assert.notStrictEqual(getFocusedCell(editor), undefined); assert.strictEqual(activeCell!.document.getText(), ''); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellCount, 4); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 1); + assert.strictEqual(editor.document.cellCount, 4); + assert.strictEqual(editor.document.getCells().indexOf(activeCell!), 1); // ---- focus bottom ---- // await vscode.commands.executeCommand('notebook.focusBottom'); - activeCell = getFocusedCell(vscode.window.activeNotebookEditor); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 3); + activeCell = getFocusedCell(editor); + assert.strictEqual(editor.document.getCells().indexOf(activeCell!), 3); // ---- focus top and then copy down ---- // await vscode.commands.executeCommand('notebook.focusTop'); - activeCell = getFocusedCell(vscode.window.activeNotebookEditor); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 0); + activeCell = getFocusedCell(editor); + assert.strictEqual(editor.document.getCells().indexOf(activeCell!), 0); await vscode.commands.executeCommand('notebook.cell.copyDown'); - activeCell = getFocusedCell(vscode.window.activeNotebookEditor); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 1); + activeCell = getFocusedCell(editor); + assert.strictEqual(editor.document.getCells().indexOf(activeCell!), 1); assert.strictEqual(activeCell?.document.getText(), 'test'); { - const focusedCell = getFocusedCell(vscode.window.activeNotebookEditor); + const focusedCell = getFocusedCell(editor); assert.strictEqual(focusedCell !== undefined, true); // delete focused cell const edit = new vscode.WorkspaceEdit(); @@ -499,45 +354,44 @@ suite('Notebook API tests', function () { await vscode.workspace.applyEdit(edit); } - activeCell = getFocusedCell(vscode.window.activeNotebookEditor); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 1); + activeCell = getFocusedCell(editor); + assert.strictEqual(editor.document.getCells().indexOf(activeCell!), 1); assert.strictEqual(activeCell?.document.getText(), ''); // ---- focus top and then copy up ---- // await vscode.commands.executeCommand('notebook.focusTop'); await vscode.commands.executeCommand('notebook.cell.copyUp'); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellCount, 5); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellAt(0).document.getText(), 'test'); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellAt(1).document.getText(), 'test'); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellAt(2).document.getText(), ''); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.cellAt(3).document.getText(), ''); - activeCell = getFocusedCell(vscode.window.activeNotebookEditor); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 0); + assert.strictEqual(editor.document.cellCount, 5); + assert.strictEqual(editor.document.cellAt(0).document.getText(), 'test'); + assert.strictEqual(editor.document.cellAt(1).document.getText(), 'test'); + assert.strictEqual(editor.document.cellAt(2).document.getText(), ''); + assert.strictEqual(editor.document.cellAt(3).document.getText(), ''); + activeCell = getFocusedCell(editor); + assert.strictEqual(editor.document.getCells().indexOf(activeCell!), 0); // ---- move up and down ---- // await vscode.commands.executeCommand('notebook.cell.moveDown'); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(getFocusedCell(vscode.window.activeNotebookEditor)!), 1, - `first move down, active cell ${getFocusedCell(vscode.window.activeNotebookEditor)!.document.uri.toString()}, ${getFocusedCell(vscode.window.activeNotebookEditor)!.document.getText()}`); + assert.strictEqual(editor.document.getCells().indexOf(getFocusedCell(editor)!), 1, + `first move down, active cell ${getFocusedCell(editor)!.document.uri.toString()}, ${getFocusedCell(editor)!.document.getText()}`); await vscode.commands.executeCommand('workbench.action.files.save'); await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); }); - test('move cells will not recreate cells in ExtHost', async function () { + test('editor move command - event and move cells will not recreate cells in ExtHost (#98126)', async function () { const notebook = await openRandomNotebookDocument(); - await vscode.window.showNotebookDocument(notebook); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - await vscode.commands.executeCommand('notebook.focusTop'); + const editor = await vscode.window.showNotebookDocument(notebook); - const activeCell = getFocusedCell(vscode.window.activeNotebookEditor); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 0); - await vscode.commands.executeCommand('notebook.cell.moveDown'); + const activeCell = getFocusedCell(editor); + assert.strictEqual(activeCell?.index, 0); + const moveChange = asPromise(vscode.notebooks.onDidChangeNotebookCells); await vscode.commands.executeCommand('notebook.cell.moveDown'); + assert.ok(await moveChange); - const newActiveCell = getFocusedCell(vscode.window.activeNotebookEditor); + const newActiveCell = getFocusedCell(editor); + assert.strictEqual(newActiveCell?.index, 1); assert.deepStrictEqual(activeCell, newActiveCell); }); @@ -674,18 +528,35 @@ suite('Notebook API tests', function () { const cell = editor.document.cellAt(0); + const alternativeKernel = new class extends Kernel { + constructor() { + super('secondaryKernel', 'Notebook Secondary Test Kernel'); + this.controller.supportsExecutionOrder = false; + } + + override async _runCell(cell: vscode.NotebookCell) { + const task = this.controller.createNotebookCellExecution(cell); + task.start(); + await task.replaceOutput([new vscode.NotebookCellOutput([ + vscode.NotebookCellOutputItem.text('my second output', 'text/plain') + ])]); + task.end(true); + } + }; + testDisposables.push(alternativeKernel.controller); + await withEvent(vscode.notebooks.onDidChangeCellOutputs, async (event) => { - await assertKernel(kernel1, notebook); + await assertKernel(defaultKernel, notebook); await vscode.commands.executeCommand('notebook.cell.execute'); await event; assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked assert.strictEqual(cell.outputs[0].items.length, 1); assert.strictEqual(cell.outputs[0].items[0].mime, 'text/plain'); - assert.deepStrictEqual(new TextDecoder().decode(cell.outputs[0].items[0].data), 'my output'); + assert.deepStrictEqual(new TextDecoder().decode(cell.outputs[0].items[0].data), cell.document.getText()); }); await withEvent(vscode.notebooks.onDidChangeCellOutputs, async (event) => { - await assertKernel(kernel2, notebook); + await assertKernel(alternativeKernel, notebook); await vscode.commands.executeCommand('notebook.cell.execute'); await event; assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked @@ -1134,74 +1005,44 @@ suite('Notebook API tests', function () { assert.strictEqual(cell.executionSummary?.timing?.endTime, 20); }); +}); +suite('statusbar', () => { + const emitter = new vscode.EventEmitter(); + const onDidCallProvide = emitter.event; + const suiteDisposables: vscode.Disposable[] = []; + suiteTeardown(async function () { + assertNoRpc(); - suite('statusbar', () => { - const emitter = new vscode.EventEmitter(); - const onDidCallProvide = emitter.event; - suiteSetup(() => { - vscode.notebooks.registerNotebookCellStatusBarItemProvider('notebookCoreTest', { - async provideCellStatusBarItems(cell: vscode.NotebookCell, _token: vscode.CancellationToken): Promise { - emitter.fire(cell); - return []; - } - }); - }); + await revertAllDirty(); + await closeAllEditors(); - test('provideCellStatusBarItems called on metadata change', async function () { - const provideCalled = asPromise(onDidCallProvide); - const resource = await createRandomNotebookFile(); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - await provideCalled; - - const edit = new vscode.WorkspaceEdit(); - edit.replaceNotebookCellMetadata(resource, 0, { inputCollapsed: true }); - vscode.workspace.applyEdit(edit); - await provideCalled; - }); + disposeAll(suiteDisposables); + suiteDisposables.length = 0; }); - // }); + suiteSetup(() => { + suiteDisposables.push(vscode.notebooks.registerNotebookCellStatusBarItemProvider('notebookCoreTest', { + async provideCellStatusBarItems(cell: vscode.NotebookCell, _token: vscode.CancellationToken): Promise { + emitter.fire(cell); + return []; + } + })); - // suite('webview', () => { - // for web, `asWebUri` gets `https`? - // test('asWebviewUri', async function () { - // if (vscode.env.uiKind === vscode.UIKind.Web) { - // return; - // } + suiteDisposables.push(vscode.workspace.registerNotebookContentProvider('notebookCoreTest', apiTestContentProvider)); + }); - // const resource = await createRandomNotebookFile(); - // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - // assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - // const uri = vscode.window.activeNotebookEditor!.asWebviewUri(vscode.Uri.file('./hello.png')); - // assert.strictEqual(uri.scheme, 'vscode-webview-resource'); - // await closeAllEditors(); - // }); + test('provideCellStatusBarItems called on metadata change', async function () { + const provideCalled = asPromise(onDidCallProvide); + const resource = await createRandomNotebookFile(); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await provideCalled; - - // 404 on web - // test('custom renderer message', async function () { - // if (vscode.env.uiKind === vscode.UIKind.Web) { - // return; - // } - - // const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './customRenderer.vsctestnb')); - // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - // const editor = vscode.window.activeNotebookEditor; - // const promise = new Promise(resolve => { - // const messageEmitter = editor?.onDidReceiveMessage(e => { - // if (e.type === 'custom_renderer_initialize') { - // resolve(); - // messageEmitter?.dispose(); - // } - // }); - // }); - - // await vscode.commands.executeCommand('notebook.cell.execute'); - // await promise; - // await closeAllEditors(); - // }); + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCellMetadata(resource, 0, { inputCollapsed: true }); + vscode.workspace.applyEdit(edit); + await provideCalled; + }); }); suite('Notebook API tests (metadata)', function () { diff --git a/package.json b/package.json index e1eaa6635ee..5c2e1788cbc 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,8 @@ "@types/minimist": "^1.2.1", "@types/mocha": "^8.2.0", "@types/node": "14.x", - "@types/sinon": "^1.16.36", + "@types/sinon": "^10.0.2", + "@types/sinon-test": "^2.4.2", "@types/trusted-types": "^1.0.6", "@types/vscode-windows-registry": "^1.0.0", "@types/webpack": "^4.41.25", @@ -183,7 +184,8 @@ "rcedit": "^1.1.0", "request": "^2.85.0", "rimraf": "^2.2.8", - "sinon": "^1.17.2", + "sinon": "^11.1.1", + "sinon-test": "^3.1.0", "source-map": "0.6.1", "source-map-support": "^0.3.2", "style-loader": "^1.0.0", @@ -196,7 +198,7 @@ "vinyl-fs": "^3.0.0", "vscode-debugprotocol": "1.47.0", "vscode-nls-dev": "^3.3.1", - "vscode-telemetry-extractor": "^1.7.0", + "vscode-telemetry-extractor": "^1.8.0", "webpack": "^4.43.0", "webpack-cli": "^3.3.12", "webpack-stream": "^5.2.1", diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index a28bb307fbd..d4cda8cde87 100644 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -46,7 +46,7 @@ if %errorlevel% neq 0 exit /b %errorlevel% :: Tests in the extension host -set ALL_PLATFORMS_API_TESTS_EXTRA_ARGS=--disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-keytar --disable-extensions --disable-workspace-trust --user-data-dir=%VSCODEUSERDATADIR% +set ALL_PLATFORMS_API_TESTS_EXTRA_ARGS=--disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-sandbox --no-cached-data --disable-updates --disable-keytar --disable-extensions --disable-workspace-trust --user-data-dir=%VSCODEUSERDATADIR% call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\singlefolder-tests %ALL_PLATFORMS_API_TESTS_EXTRA_ARGS% if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index 9125a2c5837..8c1b0858674 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -193,6 +193,24 @@ export class ActionRunner extends Disposable implements IActionRunner { export class Separator extends Action { + /** + * Joins all non-empty lists of actions with separators. + */ + public static join(...actionLists: readonly IAction[][]) { + let out: IAction[] = []; + for (const list of actionLists) { + if (!list.length) { + // skip + } else if (out.length) { + out = [...out, new Separator(), ...list]; + } else { + out = list; + } + } + + return out; + } + static readonly ID = 'vs.actions.separator'; constructor(label?: string) { diff --git a/src/vs/base/common/iterator.ts b/src/vs/base/common/iterator.ts index f7d81bf5bbc..4e325694222 100644 --- a/src/vs/base/common/iterator.ts +++ b/src/vs/base/common/iterator.ts @@ -30,7 +30,7 @@ export namespace Iterable { return iterable[Symbol.iterator]().next().value; } - export function some(iterable: Iterable, predicate: (t: T) => boolean): boolean { + export function some(iterable: Iterable, predicate: (t: T) => unknown): boolean { for (const element of iterable) { if (predicate(element)) { return true; diff --git a/src/vs/base/test/common/decorators.test.ts b/src/vs/base/test/common/decorators.test.ts index 87300310650..ef0268cd864 100644 --- a/src/vs/base/test/common/decorators.test.ts +++ b/src/vs/base/test/common/decorators.test.ts @@ -161,7 +161,7 @@ suite('Decorators', () => { clock.tick(200); assert.deepStrictEqual(spy.args, [[1], [5]]); - spy.reset(); + spy.resetHistory(); t.report(4); t.report(5); diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index 9d8a6c8ee37..4a50358b6b4 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -19,7 +19,7 @@ import { ModesContentHoverWidget } from 'vs/editor/contrib/hover/modesContentHov import { ModesGlyphHoverWidget } from 'vs/editor/contrib/hover/modesGlyphHover'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { editorHoverBackground, editorHoverBorder, editorHoverHighlight, textCodeBlockBackground, textLinkForeground, editorHoverStatusBarBackground, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; +import { editorHoverBackground, editorHoverBorder, editorHoverHighlight, textCodeBlockBackground, textLinkForeground, editorHoverStatusBarBackground, editorHoverForeground, textLinkActiveForeground } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { GotoDefinitionAtPositionEditorContribution } from 'vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition'; @@ -327,6 +327,10 @@ registerThemingParticipant((theme, collector) => { if (link) { collector.addRule(`.monaco-editor .monaco-hover a { color: ${link}; }`); } + const linkHover = theme.getColor(textLinkActiveForeground); + if (linkHover) { + collector.addRule(`.monaco-editor .monaco-hover a:hover { color: ${linkHover}; }`); + } const hoverForeground = theme.getColor(editorHoverForeground); if (hoverForeground) { collector.addRule(`.monaco-editor .monaco-hover { color: ${hoverForeground}; }`); diff --git a/src/vs/platform/keybinding/common/abstractKeybindingService.ts b/src/vs/platform/keybinding/common/abstractKeybindingService.ts index 6de8a0e61e5..de7210a736f 100644 --- a/src/vs/platform/keybinding/common/abstractKeybindingService.ts +++ b/src/vs/platform/keybinding/common/abstractKeybindingService.ts @@ -24,6 +24,9 @@ interface CurrentChord { label: string | null; } +// Skip logging for high-frequency text editing commands +const HIGH_FREQ_COMMANDS = /^(cursor|delete)/; + export abstract class AbstractKeybindingService extends Disposable implements IKeybindingService { public _serviceBrand: undefined; @@ -263,7 +266,9 @@ export abstract class AbstractKeybindingService extends Disposable implements IK } else { this._commandService.executeCommand(resolveResult.commandId, resolveResult.commandArgs).then(undefined, err => this._notificationService.warn(err)); } - this._telemetryService.publicLog2('workbenchActionExecuted', { id: resolveResult.commandId, from: 'keybinding' }); + if (!HIGH_FREQ_COMMANDS.test(resolveResult.commandId)) { + this._telemetryService.publicLog2('workbenchActionExecuted', { id: resolveResult.commandId, from: 'keybinding' }); + } } return shouldPreventDefault; diff --git a/src/vs/platform/telemetry/test/browser/telemetryService.test.ts b/src/vs/platform/telemetry/test/browser/telemetryService.test.ts index 9b8969ffa14..e5ebba5ead4 100644 --- a/src/vs/platform/telemetry/test/browser/telemetryService.test.ts +++ b/src/vs/platform/telemetry/test/browser/telemetryService.test.ts @@ -9,10 +9,13 @@ import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry'; import { NullAppender, ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils'; import * as Errors from 'vs/base/common/errors'; import * as sinon from 'sinon'; +import * as sinonTest from 'sinon-test'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +const sinonTestFn = sinonTest(sinon); + class TestTelemetryAppender implements ITelemetryAppender { public events: any[]; @@ -85,7 +88,7 @@ class ErrorTestingSettings { suite('TelemetryService', () => { - test('Disposing', sinon.test(function () { + test('Disposing', sinonTestFn(function () { let testAppender = new TestTelemetryAppender(); let service = new TelemetryService({ appender: testAppender }, undefined!); @@ -98,7 +101,7 @@ suite('TelemetryService', () => { })); // event reporting - test('Simple event', sinon.test(function () { + test('Simple event', sinonTestFn(function () { let testAppender = new TestTelemetryAppender(); let service = new TelemetryService({ appender: testAppender }, undefined!); @@ -111,7 +114,7 @@ suite('TelemetryService', () => { }); })); - test('Event with data', sinon.test(function () { + test('Event with data', sinonTestFn(function () { let testAppender = new TestTelemetryAppender(); let service = new TelemetryService({ appender: testAppender }, undefined!); @@ -193,7 +196,7 @@ suite('TelemetryService', () => { }); }); - test('enableTelemetry on by default', sinon.test(function () { + test('enableTelemetry on by default', sinonTestFn(function () { let testAppender = new TestTelemetryAppender(); let service = new TelemetryService({ appender: testAppender }, undefined!); @@ -224,7 +227,7 @@ suite('TelemetryService', () => { } } - test('Error events', sinon.test(async function (this: any) { + test('Error events', sinonTestFn(async function (this: any) { let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); Errors.setUnexpectedErrorHandler(() => { }); @@ -256,7 +259,7 @@ suite('TelemetryService', () => { } })); - // test('Unhandled Promise Error events', sinon.test(function() { + // test('Unhandled Promise Error events', sinonTestFn(function() { // // let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); // Errors.setUnexpectedErrorHandler(() => {}); @@ -285,7 +288,7 @@ suite('TelemetryService', () => { // } // })); - test('Handle global errors', sinon.test(async function (this: any) { + test('Handle global errors', sinonTestFn(async function (this: any) { let errorStub = sinon.stub(); window.onerror = errorStub; @@ -313,7 +316,7 @@ suite('TelemetryService', () => { service.dispose(); })); - test('Error Telemetry removes PII from filename with spaces', sinon.test(async function (this: any) { + test('Error Telemetry removes PII from filename with spaces', sinonTestFn(async function (this: any) { let errorStub = sinon.stub(); window.onerror = errorStub; let settings = new ErrorTestingSettings(); @@ -336,7 +339,7 @@ suite('TelemetryService', () => { service.dispose(); })); - test('Uncaught Error Telemetry removes PII from filename', sinon.test(function (this: any) { + test('Uncaught Error Telemetry removes PII from filename', sinonTestFn(function (this: any) { let clock = this.clock; let errorStub = sinon.stub(); window.onerror = errorStub; @@ -368,7 +371,7 @@ suite('TelemetryService', () => { }); })); - test('Unexpected Error Telemetry removes PII', sinon.test(async function (this: any) { + test('Unexpected Error Telemetry removes PII', sinonTestFn(async function (this: any) { let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); Errors.setUnexpectedErrorHandler(() => { }); try { @@ -399,7 +402,7 @@ suite('TelemetryService', () => { } })); - test('Uncaught Error Telemetry removes PII', sinon.test(async function (this: any) { + test('Uncaught Error Telemetry removes PII', sinonTestFn(async function (this: any) { let errorStub = sinon.stub(); window.onerror = errorStub; let settings = new ErrorTestingSettings(); @@ -426,7 +429,7 @@ suite('TelemetryService', () => { service.dispose(); })); - test('Unexpected Error Telemetry removes PII but preserves Code file path', sinon.test(async function (this: any) { + test('Unexpected Error Telemetry removes PII but preserves Code file path', sinonTestFn(async function (this: any) { let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); Errors.setUnexpectedErrorHandler(() => { }); @@ -462,7 +465,7 @@ suite('TelemetryService', () => { } })); - test('Uncaught Error Telemetry removes PII but preserves Code file path', sinon.test(async function (this: any) { + test('Uncaught Error Telemetry removes PII but preserves Code file path', sinonTestFn(async function (this: any) { let errorStub = sinon.stub(); window.onerror = errorStub; let settings = new ErrorTestingSettings(); @@ -491,7 +494,7 @@ suite('TelemetryService', () => { service.dispose(); })); - test('Unexpected Error Telemetry removes PII but preserves Code file path with node modules', sinon.test(async function (this: any) { + test('Unexpected Error Telemetry removes PII but preserves Code file path with node modules', sinonTestFn(async function (this: any) { let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); Errors.setUnexpectedErrorHandler(() => { }); @@ -523,7 +526,7 @@ suite('TelemetryService', () => { } })); - test('Uncaught Error Telemetry removes PII but preserves Code file path', sinon.test(async function (this: any) { + test('Uncaught Error Telemetry removes PII but preserves Code file path', sinonTestFn(async function (this: any) { let errorStub = sinon.stub(); window.onerror = errorStub; let settings = new ErrorTestingSettings(); @@ -549,7 +552,7 @@ suite('TelemetryService', () => { })); - test('Unexpected Error Telemetry removes PII but preserves Code file path when PIIPath is configured', sinon.test(async function (this: any) { + test('Unexpected Error Telemetry removes PII but preserves Code file path when PIIPath is configured', sinonTestFn(async function (this: any) { let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); Errors.setUnexpectedErrorHandler(() => { }); @@ -585,7 +588,7 @@ suite('TelemetryService', () => { } })); - test('Uncaught Error Telemetry removes PII but preserves Code file path when PIIPath is configured', sinon.test(async function (this: any) { + test('Uncaught Error Telemetry removes PII but preserves Code file path when PIIPath is configured', sinonTestFn(async function (this: any) { let errorStub = sinon.stub(); window.onerror = errorStub; let settings = new ErrorTestingSettings(); @@ -614,7 +617,7 @@ suite('TelemetryService', () => { service.dispose(); })); - test('Unexpected Error Telemetry removes PII but preserves Missing Model error message', sinon.test(async function (this: any) { + test('Unexpected Error Telemetry removes PII but preserves Missing Model error message', sinonTestFn(async function (this: any) { let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); Errors.setUnexpectedErrorHandler(() => { }); @@ -650,7 +653,7 @@ suite('TelemetryService', () => { } })); - test('Uncaught Error Telemetry removes PII but preserves Missing Model error message', sinon.test(async function (this: any) { + test('Uncaught Error Telemetry removes PII but preserves Missing Model error message', sinonTestFn(async function (this: any) { let errorStub = sinon.stub(); window.onerror = errorStub; let settings = new ErrorTestingSettings(); @@ -680,7 +683,7 @@ suite('TelemetryService', () => { service.dispose(); })); - test('Unexpected Error Telemetry removes PII but preserves No Such File error message', sinon.test(async function (this: any) { + test('Unexpected Error Telemetry removes PII but preserves No Such File error message', sinonTestFn(async function (this: any) { let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); Errors.setUnexpectedErrorHandler(() => { }); @@ -716,7 +719,7 @@ suite('TelemetryService', () => { } })); - test('Uncaught Error Telemetry removes PII but preserves No Such File error message', sinon.test(async function (this: any) { + test('Uncaught Error Telemetry removes PII but preserves No Such File error message', sinonTestFn(async function (this: any) { let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); Errors.setUnexpectedErrorHandler(() => { }); @@ -754,7 +757,7 @@ suite('TelemetryService', () => { } })); - test('Telemetry Service sends events when enableTelemetry is on', sinon.test(function () { + test('Telemetry Service sends events when enableTelemetry is on', sinonTestFn(function () { let testAppender = new TestTelemetryAppender(); let service = new TelemetryService({ appender: testAppender }, undefined!); diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 77b7c5465dc..327f4f98c18 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -1989,7 +1989,7 @@ declare module 'vscode' { * { language: 'typescript', scheme: 'file' } * * @example A language filter that applies to all package.json paths - * { language: 'json', scheme: 'untitled', pattern: '**​/package.json' } + * { language: 'json', pattern: '**​/package.json' } */ export interface DocumentFilter { @@ -2815,9 +2815,9 @@ declare module 'vscode' { } /** - * The inline values provider interface defines the contract between extensions and the VS Code debugger inline values feature. + * The inline values provider interface defines the contract between extensions and the editor's debugger inline values feature. * In this contract the provider returns inline value information for a given document range - * and VS Code shows this information in the editor at the end of lines. + * and the editor shows this information in the editor at the end of lines. */ export interface InlineValuesProvider { @@ -2829,7 +2829,7 @@ declare module 'vscode' { /** * Provide "inline value" information for a given document and range. - * VS Code calls this method whenever debugging stops in the given document. + * The editor calls this method whenever debugging stops in the given document. * The returned inline values information is rendered in the editor at the end of lines. * * @param document The document for which the inline values information is needed. @@ -3646,7 +3646,7 @@ declare module 'vscode' { * ``` * * @see {@link SemanticTokensBuilder} for a helper to encode tokens as integers. - * *NOTE*: When doing edits, it is possible that multiple edits occur until VS Code decides to invoke the semantic tokens provider. + * *NOTE*: When doing edits, it is possible that multiple edits occur until the editor decides to invoke the semantic tokens provider. * *NOTE*: If the provider cannot temporarily compute semantic tokens, it can indicate this by throwing an error with the message 'Busy'. */ provideDocumentSemanticTokens(document: TextDocument, token: CancellationToken): ProviderResult; @@ -5556,7 +5556,7 @@ declare module 'vscode' { /** * Role of the widget which defines how a screen reader interacts with it. * The role should be set in special cases when for example a tree-like element behaves like a checkbox. - * If role is not specified VS Code will pick the appropriate role automatically. + * If role is not specified the editor will pick the appropriate role automatically. * More about aria roles can be found here https://w3c.github.io/aria/#widget_roles */ role?: string; @@ -5648,7 +5648,7 @@ declare module 'vscode' { * The command must be {@link commands.getCommands known}. * * Note that if this is a {@link Command `Command`} object, only the {@link Command.command `command`} and {@link Command.arguments `arguments`} - * are used by VS Code. + * are used by the editor. */ command: string | Command | undefined; @@ -5959,13 +5959,13 @@ declare module 'vscode' { export enum ExtensionMode { /** * The extension is installed normally (for example, from the marketplace - * or VSIX) in VS Code. + * or VSIX) in the editor. */ Production = 1, /** * The extension is running from an `--extensionDevelopmentPath` provided - * when launching VS Code. + * when launching the editor. */ Development = 2, @@ -6549,7 +6549,7 @@ declare module 'vscode' { constructor(commandLine: string, options?: ShellExecutionOptions); /** - * Creates a shell execution with a command and arguments. For the real execution VS Code will + * Creates a shell execution with a command and arguments. For the real execution the editor will * construct a command line from the command and the arguments. This is subject to interpretation * especially when it comes to quoting. If full control over the command line is needed please * use the constructor that creates a `ShellExecution` with the full command line. @@ -6865,7 +6865,7 @@ declare module 'vscode' { export function fetchTasks(filter?: TaskFilter): Thenable; /** - * Executes a task that is managed by VS Code. The returned + * Executes a task that is managed by the editor. The returned * task execution can be used to terminate the task. * * @throws When running a ShellExecution or a ProcessExecution @@ -7281,7 +7281,7 @@ declare module 'vscode' { * @param scheme The scheme of the filesystem, for example `file` or `git`. * * @return `true` if the file system supports writing, `false` if it does not - * support writing (i.e. it is readonly), and `undefined` if VS Code does not + * support writing (i.e. it is readonly), and `undefined` if the editor does not * know about the filesystem. */ isWritableFileSystem(scheme: string): boolean | undefined; @@ -7362,7 +7362,7 @@ declare module 'vscode' { * Webviews are sandboxed from normal extension process, so all communication with the webview must use * message passing. To send a message from the extension to the webview, use {@link Webview.postMessage `postMessage`}. * To send message from the webview back to an extension, use the `acquireVsCodeApi` function inside the webview - * to get a handle to VS Code's api and then call `.postMessage()`: + * to get a handle to the editor's api and then call `.postMessage()`: * * ```html * `; + render(output: ICellOutputViewModel, item: IOutputItemDto, container: HTMLElement, notebookUri: URI): IRenderOutput { + + const str = getStringValue(item); + const scriptVal = ``; - }); return { type: RenderOutputType.Html, source: output, @@ -72,8 +70,8 @@ class CodeRendererContrib extends Disposable implements IOutputRendererContribut super(); } - render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement): IRenderOutput { - const value = items.map(getStringValue).join(''); + render(output: ICellOutputViewModel, item: IOutputItemDto, container: HTMLElement): IRenderOutput { + const value = getStringValue(item); return this._render(output, container, value, 'javascript'); } @@ -106,8 +104,8 @@ class JSONRendererContrib extends CodeRendererContrib { return ['application/json']; } - override render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement): IRenderOutput { - const str = items.map(getStringValue).join(''); + override render(output: ICellOutputViewModel, item: IOutputItemDto, container: HTMLElement): IRenderOutput { + const str = getStringValue(item); return this._render(output, container, str, 'jsonc'); } } @@ -130,15 +128,13 @@ class StreamRendererContrib extends Disposable implements IOutputRendererContrib super(); } - render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, notebookUri: URI): IRenderOutput { + render(output: ICellOutputViewModel, item: IOutputItemDto, container: HTMLElement, notebookUri: URI): IRenderOutput { const linkDetector = this.instantiationService.createInstance(LinkDetector); - items.forEach(item => { - const text = getStringValue(item); - const contentNode = DOM.$('span.output-stream'); - truncatedArrayOfString(notebookUri!, output.cellViewModel, contentNode, [text], linkDetector, this.openerService, this.themeService); - container.appendChild(contentNode); - }); + const text = getStringValue(item); + const contentNode = DOM.$('span.output-stream'); + truncatedArrayOfString(notebookUri, output.cellViewModel, contentNode, [text], linkDetector, this.openerService, this.themeService); + container.appendChild(contentNode); return { type: RenderOutputType.Mainframe }; } @@ -153,8 +149,8 @@ class StderrRendererContrib extends StreamRendererContrib { return ['application/vnd.code.notebook.stderr', 'application/x.notebook.stderr']; } - override render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, notebookUri: URI): IRenderOutput { - const result = super.render(output, items, container, notebookUri); + override render(output: ICellOutputViewModel, item: IOutputItemDto, container: HTMLElement, notebookUri: URI): IRenderOutput { + const result = super.render(output, item, container, notebookUri); container.classList.add('error'); return result; } @@ -181,35 +177,34 @@ class JSErrorRendererContrib implements IOutputRendererContribution { return ['application/vnd.code.notebook.error']; } - render(_output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, _notebookUri: URI): IRenderOutput { + render(_output: ICellOutputViewModel, item: IOutputItemDto, container: HTMLElement, _notebookUri: URI): IRenderOutput { const linkDetector = this._instantiationService.createInstance(LinkDetector); type ErrorLike = Partial; - for (let item of items) { - let err: ErrorLike; - try { - err = JSON.parse(getStringValue(item)); - } catch (e) { - this._logService.warn('INVALID output item (failed to parse)', e); - continue; - } - const header = document.createElement('div'); - const headerMessage = err.name && err.message ? `${err.name}: ${err.message}` : err.name || err.message; - if (headerMessage) { - header.innerText = headerMessage; - container.appendChild(header); - } - const stack = document.createElement('pre'); - stack.classList.add('traceback'); - if (err.stack) { - stack.appendChild(handleANSIOutput(err.stack, linkDetector, this._themeService, undefined)); - } - container.appendChild(stack); - container.classList.add('error'); + let err: ErrorLike; + try { + err = JSON.parse(getStringValue(item)); + } catch (e) { + this._logService.warn('INVALID output item (failed to parse)', e); + return { type: RenderOutputType.Mainframe }; } + const header = document.createElement('div'); + const headerMessage = err.name && err.message ? `${err.name}: ${err.message}` : err.name || err.message; + if (headerMessage) { + header.innerText = headerMessage; + container.appendChild(header); + } + const stack = document.createElement('pre'); + stack.classList.add('traceback'); + if (err.stack) { + stack.appendChild(handleANSIOutput(err.stack, linkDetector, this._themeService, undefined)); + } + container.appendChild(stack); + container.classList.add('error'); + return { type: RenderOutputType.Mainframe }; } } @@ -232,12 +227,12 @@ class PlainTextRendererContrib extends Disposable implements IOutputRendererCont super(); } - render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, notebookUri: URI): IRenderOutput { + render(output: ICellOutputViewModel, item: IOutputItemDto, container: HTMLElement, notebookUri: URI): IRenderOutput { const linkDetector = this.instantiationService.createInstance(LinkDetector); - const str = items.map(getStringValue); + const str = getStringValue(item); const contentNode = DOM.$('.output-plaintext'); - truncatedArrayOfString(notebookUri!, output.cellViewModel, contentNode, str, linkDetector, this.openerService, this.themeService); + truncatedArrayOfString(notebookUri, output.cellViewModel, contentNode, [str], linkDetector, this.openerService, this.themeService); container.appendChild(contentNode); return { type: RenderOutputType.Mainframe, supportAppend: true }; @@ -259,8 +254,8 @@ class HTMLRendererContrib extends Disposable implements IOutputRendererContribut super(); } - render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, notebookUri: URI): IRenderOutput { - const str = items.map(getStringValue).join(''); + render(output: ICellOutputViewModel, item: IOutputItemDto, container: HTMLElement, notebookUri: URI): IRenderOutput { + const str = getStringValue(item); return { type: RenderOutputType.Html, source: output, @@ -285,16 +280,14 @@ class MdRendererContrib extends Disposable implements IOutputRendererContributio super(); } - render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, notebookUri: URI): IRenderOutput { + render(output: ICellOutputViewModel, item: IOutputItemDto, container: HTMLElement, notebookUri: URI): IRenderOutput { const disposable = new DisposableStore(); - for (let item of items) { - const str = getStringValue(item); - const mdOutput = document.createElement('div'); - const mdRenderer = this.instantiationService.createInstance(MarkdownRenderer, { baseUrl: dirname(notebookUri) }); - mdOutput.appendChild(mdRenderer.render({ value: str, isTrusted: true, supportThemeIcons: true }, undefined, { gfm: true }).element); - container.appendChild(mdOutput); - disposable.add(mdRenderer); - } + const str = getStringValue(item); + const mdOutput = document.createElement('div'); + const mdRenderer = this.instantiationService.createInstance(MarkdownRenderer, { baseUrl: dirname(notebookUri) }); + mdOutput.appendChild(mdRenderer.render({ value: str, isTrusted: true, supportThemeIcons: true }, undefined, { gfm: true }).element); + container.appendChild(mdOutput); + disposable.add(mdRenderer); return { type: RenderOutputType.Mainframe, disposable }; } } @@ -314,23 +307,21 @@ class ImgRendererContrib extends Disposable implements IOutputRendererContributi super(); } - render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, notebookUri: URI): IRenderOutput { + render(output: ICellOutputViewModel, item: IOutputItemDto, container: HTMLElement, notebookUri: URI): IRenderOutput { const disposable = new DisposableStore(); - for (let item of items) { + const bytes = new Uint8Array(item.valueBytes); + const blob = new Blob([bytes], { type: item.mime }); + const src = URL.createObjectURL(blob); + disposable.add(toDisposable(() => URL.revokeObjectURL(src))); - const bytes = new Uint8Array(item.valueBytes); - const blob = new Blob([bytes], { type: item.mime }); - const src = URL.createObjectURL(blob); - disposable.add(toDisposable(() => URL.revokeObjectURL(src))); + const image = document.createElement('img'); + image.src = src; + const display = document.createElement('div'); + display.classList.add('display'); + display.appendChild(image); + container.appendChild(display); - const image = document.createElement('img'); - image.src = src; - const display = document.createElement('div'); - display.classList.add('display'); - display.appendChild(image); - container.appendChild(display); - } return { type: RenderOutputType.Mainframe, disposable }; } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index a04712afd06..6d267dcabe4 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -26,7 +26,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { asWebviewUri } from 'vs/workbench/api/common/shared/webview'; import { CellEditState, ICellOutputViewModel, ICommonCellInfo, ICommonNotebookEditor, IDisplayOutputLayoutUpdateRequest, IDisplayOutputViewModel, IGenericCellViewModel, IInsetRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { PreloadOptions, preloadsScriptStr, RendererMetadata } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads'; +import { preloadsScriptStr, RendererMetadata } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads'; import { transformWebviewThemeVars } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewThemeMapping'; import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { INotebookKernel, INotebookRendererInfo, RendererMessagingSpec } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -34,368 +34,7 @@ import { IScopedRendererMessaging } from 'vs/workbench/contrib/notebook/common/n import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IWebviewService, WebviewContentPurpose, WebviewElement } from 'vs/workbench/contrib/webview/browser/webview'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; - -interface BaseToWebviewMessage { - readonly __vscode_notebook_message: true; -} - -export interface WebviewIntialized extends BaseToWebviewMessage { - type: 'initialized'; -} - -export interface DimensionUpdate { - id: string; - init?: boolean; - height: number; - isOutput?: boolean; -} - -export interface IDimensionMessage extends BaseToWebviewMessage { - type: 'dimension'; - updates: readonly DimensionUpdate[]; -} - -export interface IMouseEnterMessage extends BaseToWebviewMessage { - type: 'mouseenter'; - id: string; -} - -export interface IMouseLeaveMessage extends BaseToWebviewMessage { - type: 'mouseleave'; - id: string; -} - -export interface IOutputFocusMessage extends BaseToWebviewMessage { - type: 'outputFocus'; - id: string; -} - -export interface IOutputBlurMessage extends BaseToWebviewMessage { - type: 'outputBlur'; - id: string; -} - -export interface IWheelMessage extends BaseToWebviewMessage { - type: 'did-scroll-wheel'; - payload: any; -} - -export interface IScrollAckMessage extends BaseToWebviewMessage { - type: 'scroll-ack'; - data: { top: number }; - version: number; -} - -export interface IBlurOutputMessage extends BaseToWebviewMessage { - type: 'focus-editor'; - id: string; - focusNext?: boolean; -} - -export interface IClickedDataUrlMessage extends BaseToWebviewMessage { - type: 'clicked-data-url'; - data: string | ArrayBuffer | null; - downloadName?: string; -} - -export interface IClickMarkdownPreviewMessage extends BaseToWebviewMessage { - readonly type: 'clickMarkdownPreview'; - readonly cellId: string; - readonly ctrlKey: boolean - readonly altKey: boolean; - readonly metaKey: boolean; - readonly shiftKey: boolean; -} - -export interface IContextMenuMarkdownPreviewMessage extends BaseToWebviewMessage { - readonly type: 'contextMenuMarkdownPreview'; - readonly cellId: string; - readonly clientX: number; - readonly clientY: number; -} - -export interface IMouseEnterMarkdownPreviewMessage extends BaseToWebviewMessage { - type: 'mouseEnterMarkdownPreview'; - cellId: string; -} - -export interface IMouseLeaveMarkdownPreviewMessage extends BaseToWebviewMessage { - type: 'mouseLeaveMarkdownPreview'; - cellId: string; -} - -export interface IToggleMarkdownPreviewMessage extends BaseToWebviewMessage { - type: 'toggleMarkdownPreview'; - cellId: string; -} - -export interface ICellDragStartMessage extends BaseToWebviewMessage { - type: 'cell-drag-start'; - readonly cellId: string; - readonly dragOffsetY: number; -} - -export interface ICellDragMessage extends BaseToWebviewMessage { - type: 'cell-drag'; - readonly cellId: string; - readonly dragOffsetY: number; -} - -export interface ICellDropMessage extends BaseToWebviewMessage { - readonly type: 'cell-drop'; - readonly cellId: string; - readonly ctrlKey: boolean - readonly altKey: boolean; - readonly dragOffsetY: number; -} - -export interface ICellDragEndMessage extends BaseToWebviewMessage { - readonly type: 'cell-drag-end'; - readonly cellId: string; -} - -export interface IInitializedMarkdownPreviewMessage extends BaseToWebviewMessage { - readonly type: 'initializedMarkdownPreview'; -} - -export interface ITelemetryFoundRenderedMarkdownMath extends BaseToWebviewMessage { - readonly type: 'telemetryFoundRenderedMarkdownMath'; -} - -export interface ITelemetryFoundUnrenderedMarkdownMath extends BaseToWebviewMessage { - readonly type: 'telemetryFoundUnrenderedMarkdownMath'; - readonly latexDirective: string; -} - -export interface IClearMessage { - type: 'clear'; -} - -export interface IOutputRequestMetadata { - /** - * Additional attributes of a cell metadata. - */ - custom?: { [key: string]: unknown }; -} - -export interface IOutputRequestDto { - /** - * { mime_type: value } - */ - data: { [key: string]: unknown; } - - metadata?: IOutputRequestMetadata; - outputId: string; -} - -export interface ICreationRequestMessage { - type: 'html'; - content: - | { type: RenderOutputType.Html; htmlContent: string } - | { type: RenderOutputType.Extension; outputId: string; valueBytes: Uint8Array, metadata: unknown; metadata2: unknown, mimeType: string }; - cellId: string; - outputId: string; - cellTop: number; - outputOffset: number; - left: number; - requiredPreloads: ReadonlyArray; - readonly initiallyHidden?: boolean; - rendererId?: string | undefined; -} - -export interface IContentWidgetTopRequest { - outputId: string; - cellTop: number; - outputOffset: number; - forceDisplay: boolean; -} - -export interface IViewScrollTopRequestMessage { - type: 'view-scroll'; - widgets: IContentWidgetTopRequest[]; - markdownPreviews: { id: string; top: number }[]; -} - -export interface IScrollRequestMessage { - type: 'scroll'; - id: string; - top: number; - widgetTop?: number; - version: number; -} - -export interface IClearOutputRequestMessage { - type: 'clearOutput'; - cellId: string; - outputId: string; - cellUri: string; - rendererId: string | undefined; -} - -export interface IHideOutputMessage { - type: 'hideOutput'; - outputId: string; - cellId: string; -} - -export interface IShowOutputMessage { - type: 'showOutput'; - cellId: string; - outputId: string; - cellTop: number; - outputOffset: number; -} - -export interface IFocusOutputMessage { - type: 'focus-output'; - cellId: string; -} - -export interface IAckOutputHeightMessage { - type: 'ack-dimension', - cellId: string; - outputId: string; - height: number; -} - - -export interface IControllerPreload { - originalUri: string; - uri: string; -} - -export interface IUpdateControllerPreloadsMessage { - type: 'preload'; - resources: IControllerPreload[]; -} - -export interface IUpdateDecorationsMessage { - type: 'decorations'; - cellId: string; - addedClassNames: string[]; - removedClassNames: string[]; -} - -export interface ICustomKernelMessage extends BaseToWebviewMessage { - type: 'customKernelMessage'; - message: unknown; -} - -export interface ICustomRendererMessage extends BaseToWebviewMessage { - type: 'customRendererMessage'; - rendererId: string; - message: unknown; -} - -export interface ICreateMarkdownMessage { - type: 'createMarkdownPreview', - cell: IMarkdownCellInitialization; -} -export interface IDeleteMarkdownMessage { - type: 'deleteMarkdownPreview', - ids: readonly string[]; -} - -export interface IHideMarkdownMessage { - type: 'hideMarkdownPreviews'; - ids: readonly string[]; -} - -export interface IUnhideMarkdownMessage { - type: 'unhideMarkdownPreviews'; - ids: readonly string[]; -} - -export interface IShowMarkdownMessage { - type: 'showMarkdownPreview', - id: string; - handle: number; - content: string | undefined; - top: number; -} - -export interface IUpdateSelectedMarkdownPreviews { - readonly type: 'updateSelectedMarkdownPreviews', - readonly selectedCellIds: readonly string[] -} - -export interface IMarkdownCellInitialization { - cellId: string; - cellHandle: number; - content: string; - offset: number; - visible: boolean; -} - -export interface IInitializeMarkdownMessage { - type: 'initializeMarkdownPreview'; - cells: ReadonlyArray; -} - -export interface INotebookStylesMessage { - type: 'notebookStyles'; - styles: { - [key: string]: string; - }; -} - -export interface INotebookOptionsMessage { - type: 'notebookOptions'; - options: PreloadOptions; -} - -export type FromWebviewMessage = - | WebviewIntialized - | IDimensionMessage - | IMouseEnterMessage - | IMouseLeaveMessage - | IOutputFocusMessage - | IOutputBlurMessage - | IWheelMessage - | IScrollAckMessage - | IBlurOutputMessage - | ICustomKernelMessage - | ICustomRendererMessage - | IClickedDataUrlMessage - | IClickMarkdownPreviewMessage - | IContextMenuMarkdownPreviewMessage - | IMouseEnterMarkdownPreviewMessage - | IMouseLeaveMarkdownPreviewMessage - | IToggleMarkdownPreviewMessage - | ICellDragStartMessage - | ICellDragMessage - | ICellDropMessage - | ICellDragEndMessage - | IInitializedMarkdownPreviewMessage - | ITelemetryFoundRenderedMarkdownMath - | ITelemetryFoundUnrenderedMarkdownMath - ; - -export type ToWebviewMessage = - | IClearMessage - | IFocusOutputMessage - | IAckOutputHeightMessage - | ICreationRequestMessage - | IViewScrollTopRequestMessage - | IScrollRequestMessage - | IClearOutputRequestMessage - | IHideOutputMessage - | IShowOutputMessage - | IUpdateControllerPreloadsMessage - | IUpdateDecorationsMessage - | ICustomKernelMessage - | ICustomRendererMessage - | ICreateMarkdownMessage - | IDeleteMarkdownMessage - | IShowMarkdownMessage - | IHideMarkdownMessage - | IUnhideMarkdownMessage - | IUpdateSelectedMarkdownPreviews - | IInitializeMarkdownMessage - | INotebookStylesMessage - | INotebookOptionsMessage; - -export type AnyMessage = FromWebviewMessage | ToWebviewMessage; +import { ICreationRequestMessage, IMarkupCellInitialization, FromWebviewMessage, IClickedDataUrlMessage, IContentWidgetTopRequest, IControllerPreload, ToWebviewMessage } from './webviewMessages'; export interface ICachedInset { outputId: string; @@ -424,7 +63,7 @@ export class BackLayerWebView extends Disposable { element: HTMLElement; webview: WebviewElement | undefined = undefined; insetMapping: Map> = new Map(); - readonly markdownPreviewMapping = new Map(); + readonly markdownPreviewMapping = new Map(); hiddenInsetMapping: Set = new Set(); reversedInsetMapping: Map = new Map(); localResourceRootsCache: URI[] | undefined = undefined; @@ -525,7 +164,8 @@ export class BackLayerWebView extends Disposable { 'notebook-markdown-left-margin': `${this.options.markdownLeftMargin}px`, 'notebook-output-node-left-padding': `${this.options.outputNodeLeftPadding}px`, 'notebook-markdown-min-height': `${this.options.previewNodePadding * 2}px`, - 'notebook-cell-output-font-size': `${this.options.fontSize}px` + 'notebook-cell-output-font-size': `${this.options.fontSize}px`, + 'notebook-cell-markup-empty-content': nls.localize('notebook.emptyMarkdownPlaceholder', "Empty markdown cell, double click or press enter to edit."), }; } @@ -536,134 +176,6 @@ export class BackLayerWebView extends Disposable { - - -