Add support for markdown notebook renderers (#115191)

* Use shared webpack version instead of installing locally for simple-browser

* Use npm for building markdown preview

* render markdown in webview.

* update markdown preview height and offset

* Add basic custom notebook renderer point

* update css

* style update.

* update markdown header padding left

* Add example of loading katex to extend the markdown-it renderer

* Rename global to make clear it only applies to markdown-in

* hide/remove markdown preview

* Add wait for initial markdown preview rendering before showing notebook

* Add double click to switch to editing mode

* Fix markdown cells not getting updated after editing

* style polish

* notebook.experimental.useMarkdownRenderer

* switch render strategy.

* Adding very intial drag drop support for notebook markdown cells

* Implement drag/drop stubs for test classes

* Revert unrelated file changes

* Move markdown notebook math to own extension

* Add missing imports

Co-authored-by: rebornix <penn.lv@gmail.com>
This commit is contained in:
Matt Bierner 2021-02-10 16:41:53 -08:00 committed by GitHub
parent c36a09929c
commit 35f855796b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 2432 additions and 66 deletions

View file

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

View file

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

View file

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

File diff suppressed because one or more lines are too long

View file

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

View file

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

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type * as markdownIt from 'markdown-it';
import 'katex/dist/katex.min.css';
declare const extendMarkdownIt: undefined | (
(f: (md: markdownIt.MarkdownIt) => void) => void
);
(function () {
const katex = require('@iktakahiro/markdown-it-katex');
if (typeof extendMarkdownIt !== 'undefined') {
extendMarkdownIt((md: markdownIt.MarkdownIt) => {
md.use(katex);
});
}
}());

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -140,6 +140,13 @@ export interface INotebookRendererInfo {
matches(mimeType: string): boolean;
}
export interface INotebookMarkdownRendererInfo {
readonly entrypoint: URI;
readonly extensionLocation: URI;
readonly extensionId: ExtensionIdentifier;
readonly extensionIsBuiltin: boolean;
}
export interface IStreamOutput {
outputKind: CellOutputKind.Text;
text: string;

View file

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

View file

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

View file

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