mirror of
https://github.com/Microsoft/vscode
synced 2024-08-28 13:29:44 +00:00
Merge branch 'notebook/dev' into main
This commit is contained in:
commit
5b3cf7cc2a
|
@ -5,35 +5,23 @@
|
|||
|
||||
const MarkdownIt = require('markdown-it');
|
||||
|
||||
export async function activate(ctx: {
|
||||
dependencies: ReadonlyArray<{ entrypoint: string }>
|
||||
}) {
|
||||
export function activate() {
|
||||
let markdownIt = new MarkdownIt({
|
||||
html: true
|
||||
});
|
||||
|
||||
// Should we load the deps before this point?
|
||||
// Also could we await inside `renderMarkup`?
|
||||
await Promise.all(ctx.dependencies.map(async (dep) => {
|
||||
try {
|
||||
const api = await import(dep.entrypoint);
|
||||
if (api?.extendMarkdownIt) {
|
||||
markdownIt = api.extendMarkdownIt(markdownIt);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Could not load markdown entryPoint', e);
|
||||
}
|
||||
}));
|
||||
|
||||
return {
|
||||
renderMarkup: (context: { element: HTMLElement, content: string }) => {
|
||||
const rendered = markdownIt.render(context.content);
|
||||
renderCell: (_id: string, context: { element: HTMLElement, value: string }) => {
|
||||
const rendered = markdownIt.render(context.value);
|
||||
context.element.innerHTML = rendered;
|
||||
|
||||
// Insert styles into markdown preview shadow dom so that they are applied
|
||||
for (const markdownStyleNode of document.getElementsByClassName('markdown-style')) {
|
||||
context.element.insertAdjacentElement('beforebegin', markdownStyleNode.cloneNode(true) as Element);
|
||||
}
|
||||
},
|
||||
extendMarkdownIt: (f: (md: typeof markdownIt) => void) => {
|
||||
f(markdownIt);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,6 +6,12 @@ import type * as markdownIt from 'markdown-it';
|
|||
|
||||
const emoji = require('markdown-it-emoji');
|
||||
|
||||
export function extendMarkdownIt(md: markdownIt.MarkdownIt) {
|
||||
return md.use(emoji);
|
||||
export function activate(ctx: {
|
||||
getRenderer: (id: string) => any
|
||||
}) {
|
||||
const markdownItRenderer = ctx.getRenderer('markdownItRenderer');
|
||||
|
||||
markdownItRenderer.extendMarkdownIt((md: markdownIt.MarkdownIt) => {
|
||||
return md.use(emoji);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,23 +6,28 @@ import type * as markdownIt from 'markdown-it';
|
|||
|
||||
const styleHref = import.meta.url.replace(/katex.js$/, 'katex.min.css');
|
||||
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.classList.add('markdown-style');
|
||||
link.href = styleHref;
|
||||
document.head.append(link);
|
||||
export function activate(ctx: {
|
||||
getRenderer: (id: string) => any
|
||||
}) {
|
||||
const markdownItRenderer = ctx.getRenderer('markdownItRenderer');
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.classList.add('markdown-style');
|
||||
style.textContent = `
|
||||
.katex-error {
|
||||
color: var(--vscode-editorError-foreground);
|
||||
}
|
||||
`;
|
||||
document.head.append(style);
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.classList.add('markdown-style');
|
||||
link.href = styleHref;
|
||||
document.head.append(link);
|
||||
|
||||
const katex = require('@iktakahiro/markdown-it-katex');
|
||||
const style = document.createElement('style');
|
||||
style.classList.add('markdown-style');
|
||||
style.textContent = `
|
||||
.katex-error {
|
||||
color: var(--vscode-editorError-foreground);
|
||||
}
|
||||
`;
|
||||
document.head.append(style);
|
||||
|
||||
export function extendMarkdownIt(md: markdownIt.MarkdownIt) {
|
||||
return md.use(katex);
|
||||
const katex = require('@iktakahiro/markdown-it-katex');
|
||||
markdownItRenderer.extendMarkdownIt((md: markdownIt.MarkdownIt) => {
|
||||
return md.use(katex);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -25,24 +25,18 @@
|
|||
{
|
||||
"id": "markdownItRenderer-katex",
|
||||
"displayName": "Markdown it katex renderer",
|
||||
"entrypoint": "./notebook-out/katex.js",
|
||||
"mimeTypes": [
|
||||
"text/markdown"
|
||||
],
|
||||
"dependencies": [
|
||||
"markdownItRenderer"
|
||||
]
|
||||
"entrypoint": {
|
||||
"extends": "markdownItRenderer",
|
||||
"path": "./notebook-out/katex.js"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "markdownItRenderer-emoji",
|
||||
"displayName": "Markdown it emoji renderer",
|
||||
"entrypoint": "./notebook-out/emoji.js",
|
||||
"mimeTypes": [
|
||||
"text/markdown"
|
||||
],
|
||||
"dependencies": [
|
||||
"markdownItRenderer"
|
||||
]
|
||||
"entrypoint": {
|
||||
"extends": "markdownItRenderer",
|
||||
"path": "./notebook-out/emoji.js"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -47,10 +47,6 @@ suite('Notebook Document', function () {
|
|||
await utils.closeAllEditors();
|
||||
utils.disposeAll(disposables);
|
||||
disposables.length = 0;
|
||||
|
||||
for (let doc of vscode.notebook.notebookDocuments) {
|
||||
assert.strictEqual(doc.isDirty, false, doc.uri.toString());
|
||||
}
|
||||
});
|
||||
|
||||
suiteSetup(function () {
|
||||
|
@ -140,6 +136,30 @@ suite('Notebook Document', function () {
|
|||
await p;
|
||||
});
|
||||
|
||||
test('open untitled notebook', async function () {
|
||||
const nb = await vscode.notebook.openNotebookDocument('notebook.nbdserializer');
|
||||
assert.strictEqual(nb.isUntitled, true);
|
||||
assert.strictEqual(nb.isClosed, false);
|
||||
assert.strictEqual(nb.uri.scheme, 'untitled');
|
||||
// assert.strictEqual(nb.cellCount, 0); // NotebookSerializer ALWAYS returns something here
|
||||
});
|
||||
|
||||
test('open untitled with data', async function () {
|
||||
const nb = await vscode.notebook.openNotebookDocument(
|
||||
'notebook.nbdserializer',
|
||||
new vscode.NotebookData([
|
||||
new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'console.log()', 'javascript'),
|
||||
new vscode.NotebookCellData(vscode.NotebookCellKind.Markup, 'Hey', 'markdown'),
|
||||
])
|
||||
);
|
||||
assert.strictEqual(nb.isUntitled, true);
|
||||
assert.strictEqual(nb.isClosed, false);
|
||||
assert.strictEqual(nb.uri.scheme, 'untitled');
|
||||
assert.strictEqual(nb.cellCount, 2);
|
||||
assert.strictEqual(nb.cellAt(0).kind, vscode.NotebookCellKind.Code);
|
||||
assert.strictEqual(nb.cellAt(1).kind, vscode.NotebookCellKind.Markup);
|
||||
});
|
||||
|
||||
|
||||
test('workspace edit API (replaceCells)', async function () {
|
||||
const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest');
|
||||
|
|
|
@ -104,8 +104,8 @@ suite('Notebook API tests', function () {
|
|||
|
||||
suiteSetup(function () {
|
||||
suiteDisposables.push(vscode.notebook.registerNotebookContentProvider('notebookCoreTest', {
|
||||
openNotebook: async (_resource: vscode.Uri): Promise<vscode.NotebookData> => {
|
||||
if (/.*empty\-.*\.vsctestnb$/.test(_resource.path)) {
|
||||
openNotebook: async (resource: vscode.Uri): Promise<vscode.NotebookData> => {
|
||||
if (/.*empty\-.*\.vsctestnb$/.test(resource.path)) {
|
||||
return {
|
||||
metadata: new vscode.NotebookDocumentMetadata(),
|
||||
cells: []
|
||||
|
|
11
src/vs/vscode.proposed.d.ts
vendored
11
src/vs/vscode.proposed.d.ts
vendored
|
@ -1677,6 +1677,17 @@ declare module 'vscode' {
|
|||
*/
|
||||
export function openNotebookDocument(uri: Uri): Thenable<NotebookDocument>;
|
||||
|
||||
/**
|
||||
* Open an untitled notebook. The editor will prompt the user for a file
|
||||
* path when the document is to be saved.
|
||||
*
|
||||
* @see {@link openNotebookDocument}
|
||||
* @param viewType The notebook view type that should be used.
|
||||
* @param content The initial contents of the notebook.
|
||||
* @returns A promise that resolves to a {@link NotebookDocument notebook}.
|
||||
*/
|
||||
export function openNotebookDocument(viewType: string, content?: NotebookData): Thenable<NotebookDocument>;
|
||||
|
||||
/**
|
||||
* An event that is emitted when a {@link NotebookDocument notebook} is opened.
|
||||
*/
|
||||
|
|
|
@ -9,13 +9,14 @@ import { URI, UriComponents } from 'vs/base/common/uri';
|
|||
import { BoundModelReferenceCollection } from 'vs/workbench/api/browser/mainThreadDocuments';
|
||||
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
|
||||
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
|
||||
import { IImmediateCellEditOperation, IMainCellDto, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { IImmediateCellEditOperation, IMainCellDto, NotebookCellsChangeType, NotebookDataDto } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
|
||||
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
|
||||
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
|
||||
import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, MainThreadNotebookDocumentsShape } from '../common/extHost.protocol';
|
||||
import { MainThreadNotebooksAndEditors } from 'vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditors';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
export class MainThreadNotebookDocuments implements MainThreadNotebookDocumentsShape {
|
||||
|
||||
|
@ -47,7 +48,6 @@ export class MainThreadNotebookDocuments implements MainThreadNotebookDocumentsS
|
|||
this._disposables.dispose();
|
||||
this._modelReferenceCollection.dispose();
|
||||
dispose(this._documentEventListenersMapping.values());
|
||||
|
||||
}
|
||||
|
||||
private _handleNotebooksAdded(notebooks: readonly NotebookTextModel[]): void {
|
||||
|
@ -119,14 +119,48 @@ export class MainThreadNotebookDocuments implements MainThreadNotebookDocumentsS
|
|||
};
|
||||
}
|
||||
|
||||
async $tryOpenDocument(uriComponents: UriComponents): Promise<URI> {
|
||||
async $tryCreateNotebook(options: { viewType: string, content?: NotebookDataDto }): Promise<UriComponents> {
|
||||
|
||||
// find a free URI for the untitled case
|
||||
let uri: URI;
|
||||
for (let counter = 1; ; counter++) {
|
||||
let candidate = URI.from({ scheme: Schemas.untitled, path: `Untitled-${counter}`, query: options.viewType });
|
||||
if (!this._notebookService.getNotebookTextModel(candidate)) {
|
||||
uri = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const ref = await this._notebookEditorModelResolverService.resolve(uri, options.viewType);
|
||||
|
||||
// untitled notebooks are disposed when they get saved. we should not hold a reference
|
||||
// to such a disposed notebook and therefore dispose the reference as well
|
||||
ref.object.notebook.onWillDispose(() => {
|
||||
ref.dispose();
|
||||
});
|
||||
|
||||
// untitled notebooks are dirty by default
|
||||
this._proxy.$acceptDirtyStateChanged(uri, true);
|
||||
|
||||
// apply content changes... slightly HACKY -> this triggers a change event
|
||||
if (options.content) {
|
||||
ref.object.notebook.reset(
|
||||
options.content.cells,
|
||||
options.content.metadata,
|
||||
ref.object.notebook.transientOptions
|
||||
);
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
async $tryOpenNotebook(uriComponents: UriComponents): Promise<URI> {
|
||||
const uri = URI.revive(uriComponents);
|
||||
const ref = await this._notebookEditorModelResolverService.resolve(uri, undefined);
|
||||
this._modelReferenceCollection.add(uri, ref);
|
||||
return uri;
|
||||
}
|
||||
|
||||
async $trySaveDocument(uriComponents: UriComponents) {
|
||||
async $trySaveNotebook(uriComponents: UriComponents) {
|
||||
const uri = URI.revive(uriComponents);
|
||||
|
||||
const ref = await this._notebookEditorModelResolverService.resolve(uri);
|
||||
|
|
|
@ -1033,9 +1033,18 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||
|
||||
// namespace: notebook
|
||||
const notebook: typeof vscode.notebook = {
|
||||
openNotebookDocument: (uriComponents) => {
|
||||
async openNotebookDocument(uriOrOptions?: URI | string, content?: vscode.NotebookData) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostNotebook.openNotebookDocument(uriComponents);
|
||||
let uri: URI;
|
||||
if (URI.isUri(uriOrOptions)) {
|
||||
uri = uriOrOptions;
|
||||
await extHostNotebook.openNotebookDocument(uriOrOptions);
|
||||
} else if (typeof uriOrOptions === 'string') {
|
||||
uri = URI.revive(await extHostNotebook.createNotebookDocument({ viewType: uriOrOptions, content }));
|
||||
} else {
|
||||
throw new Error('Invalid arguments');
|
||||
}
|
||||
return extHostNotebook.getNotebookDocument(uri).apiNotebook;
|
||||
},
|
||||
get onDidOpenNotebookDocument(): Event<vscode.NotebookDocument> {
|
||||
checkProposedApiEnabled(extension);
|
||||
|
|
|
@ -888,8 +888,9 @@ export interface MainThreadNotebookEditorsShape extends IDisposable {
|
|||
}
|
||||
|
||||
export interface MainThreadNotebookDocumentsShape extends IDisposable {
|
||||
$tryOpenDocument(uriComponents: UriComponents): Promise<UriComponents>;
|
||||
$trySaveDocument(uri: UriComponents): Promise<boolean>;
|
||||
$tryCreateNotebook(options: { viewType: string, content?: NotebookDataDto }): Promise<UriComponents>;
|
||||
$tryOpenNotebook(uriComponents: UriComponents): Promise<UriComponents>;
|
||||
$trySaveNotebook(uri: UriComponents): Promise<boolean>;
|
||||
$applyEdits(resource: UriComponents, edits: IImmediateCellEditOperation[], computeUndoRedo?: boolean): Promise<void>;
|
||||
}
|
||||
|
||||
|
|
|
@ -254,12 +254,20 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
|||
return new NotebookEditorDecorationType(this._notebookEditorsProxy, options).value;
|
||||
}
|
||||
|
||||
async createNotebookDocument(options: { viewType: string, content?: vscode.NotebookData }): Promise<URI> {
|
||||
const canonicalUri = await this._notebookDocumentsProxy.$tryCreateNotebook({
|
||||
viewType: options.viewType,
|
||||
content: options.content && typeConverters.NotebookData.from(options.content)
|
||||
});
|
||||
return URI.revive(canonicalUri);
|
||||
}
|
||||
|
||||
async openNotebookDocument(uri: URI): Promise<vscode.NotebookDocument> {
|
||||
const cached = this._documents.get(uri);
|
||||
if (cached) {
|
||||
return cached.apiNotebook;
|
||||
}
|
||||
const canonicalUri = await this._notebookDocumentsProxy.$tryOpenDocument(uri);
|
||||
const canonicalUri = await this._notebookDocumentsProxy.$tryOpenNotebook(uri);
|
||||
const document = this._documents.get(URI.revive(canonicalUri));
|
||||
return assertIsDefined(document?.apiNotebook);
|
||||
}
|
||||
|
@ -358,19 +366,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
|||
if (!serializer) {
|
||||
throw new Error('NO serializer found');
|
||||
}
|
||||
|
||||
const data = await serializer.deserializeNotebook(bytes.buffer, token);
|
||||
const res: NotebookDataDto = {
|
||||
metadata: typeConverters.NotebookDocumentMetadata.from(data.metadata),
|
||||
cells: [],
|
||||
};
|
||||
|
||||
for (let cell of data.cells) {
|
||||
extHostTypes.NotebookCellData.validate(cell);
|
||||
res.cells.push(typeConverters.NotebookCellData.from(cell));
|
||||
}
|
||||
|
||||
return res;
|
||||
return typeConverters.NotebookData.from(data);
|
||||
}
|
||||
|
||||
async $notebookToData(handle: number, data: NotebookDataDto, token: CancellationToken): Promise<VSBuffer> {
|
||||
|
@ -378,10 +375,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
|||
if (!serializer) {
|
||||
throw new Error('NO serializer found');
|
||||
}
|
||||
const bytes = await serializer.serializeNotebook({
|
||||
metadata: typeConverters.NotebookDocumentMetadata.to(data.metadata),
|
||||
cells: data.cells.map(typeConverters.NotebookCellData.to)
|
||||
}, token);
|
||||
const bytes = await serializer.serializeNotebook(typeConverters.NotebookData.to(data), token);
|
||||
return VSBuffer.wrap(bytes);
|
||||
}
|
||||
|
||||
|
|
|
@ -258,7 +258,7 @@ export class ExtHostNotebookDocument {
|
|||
if (this._disposed) {
|
||||
return Promise.reject(new Error('Notebook has been closed'));
|
||||
}
|
||||
return this._proxy.$trySaveDocument(this.uri);
|
||||
return this._proxy.$trySaveNotebook(this.uri);
|
||||
}
|
||||
|
||||
private _spliceNotebookCells(splices: NotebookCellsSplice2[], initialization: boolean): void {
|
||||
|
|
|
@ -1485,6 +1485,28 @@ export namespace NotebookCellKind {
|
|||
}
|
||||
}
|
||||
|
||||
export namespace NotebookData {
|
||||
|
||||
export function from(data: vscode.NotebookData): notebooks.NotebookDataDto {
|
||||
const res: notebooks.NotebookDataDto = {
|
||||
metadata: NotebookDocumentMetadata.from(data.metadata),
|
||||
cells: [],
|
||||
};
|
||||
for (let cell of data.cells) {
|
||||
types.NotebookCellData.validate(cell);
|
||||
res.cells.push(NotebookCellData.from(cell));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
export function to(data: notebooks.NotebookDataDto): vscode.NotebookData {
|
||||
return {
|
||||
metadata: NotebookDocumentMetadata.to(data.metadata),
|
||||
cells: data.cells.map(NotebookCellData.to)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export namespace NotebookCellData {
|
||||
|
||||
export function from(data: vscode.NotebookCellData): notebooks.ICellDto2 {
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// Scrollable Element
|
||||
|
||||
export const SCROLLABLE_ELEMENT_PADDING_TOP = 18;
|
|
@ -1039,7 +1039,11 @@ MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, {
|
|||
},
|
||||
order: -5,
|
||||
group: 'navigation/add',
|
||||
when: NOTEBOOK_EDITOR_EDITABLE.isEqualTo(true)
|
||||
when: ContextKeyExpr.and(
|
||||
NOTEBOOK_EDITOR_EDITABLE.isEqualTo(true),
|
||||
ContextKeyExpr.notEquals('config.notebook.experimental.insertToolbarPosition', 'betweenCells'),
|
||||
ContextKeyExpr.notEquals('config.notebook.experimental.insertToolbarPosition', 'hidden')
|
||||
)
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.NotebookCellListTop, {
|
||||
|
@ -1105,7 +1109,11 @@ MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, {
|
|||
},
|
||||
order: -5,
|
||||
group: 'navigation/add',
|
||||
when: NOTEBOOK_EDITOR_EDITABLE.isEqualTo(true)
|
||||
when: ContextKeyExpr.and(
|
||||
NOTEBOOK_EDITOR_EDITABLE.isEqualTo(true),
|
||||
ContextKeyExpr.notEquals('config.notebook.experimental.insertToolbarPosition', 'betweenCells'),
|
||||
ContextKeyExpr.notEquals('config.notebook.experimental.insertToolbarPosition', 'hidden')
|
||||
)
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.NotebookCellListTop, {
|
||||
|
|
|
@ -242,6 +242,12 @@ export class KernelStatus extends Disposable implements IWorkbenchContribution {
|
|||
}
|
||||
|
||||
const updateStatus = () => {
|
||||
if (activeEditor.notebookOptions.getLayoutConfiguration().globalToolbar) {
|
||||
// kernel info rendered in the notebook toolbar already
|
||||
this._kernelInfoElement.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const notebook = activeEditor.viewModel?.notebookDocument;
|
||||
if (notebook) {
|
||||
this._showKernelStatus(notebook);
|
||||
|
@ -254,6 +260,7 @@ export class KernelStatus extends Disposable implements IWorkbenchContribution {
|
|||
this._editorDisposables.add(this._notebookKernelService.onDidChangeNotebookKernelBinding(updateStatus));
|
||||
this._editorDisposables.add(this._notebookKernelService.onDidChangeNotebookAffinity(updateStatus));
|
||||
this._editorDisposables.add(activeEditor.onDidChangeModel(updateStatus));
|
||||
this._editorDisposables.add(activeEditor.notebookOptions.onDidChangeOptions(updateStatus));
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import * as nls from 'vs/nls';
|
||||
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { NotebookEditorPriority } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { NotebookEditorPriority, NotebookRendererEntrypoint } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
|
||||
namespace NotebookEditorContribution {
|
||||
export const viewType = 'viewType';
|
||||
|
@ -37,7 +37,7 @@ export interface INotebookRendererContribution {
|
|||
readonly [NotebookRendererContribution.viewType]?: string;
|
||||
readonly [NotebookRendererContribution.displayName]: string;
|
||||
readonly [NotebookRendererContribution.mimeTypes]?: readonly string[];
|
||||
readonly [NotebookRendererContribution.entrypoint]: string;
|
||||
readonly [NotebookRendererContribution.entrypoint]: NotebookRendererEntrypoint;
|
||||
readonly [NotebookRendererContribution.hardDependencies]: readonly string[];
|
||||
readonly [NotebookRendererContribution.optionalDependencies]: readonly string[];
|
||||
}
|
||||
|
@ -130,8 +130,27 @@ const notebookRendererContribution: IJSONSchema = {
|
|||
}
|
||||
},
|
||||
[NotebookRendererContribution.entrypoint]: {
|
||||
type: 'string',
|
||||
description: nls.localize('contributes.notebook.renderer.entrypoint', 'File to load in the webview to render the extension.'),
|
||||
oneOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
// todo@connor4312 + @mjbvz: uncomment this once it's ready for external adoption
|
||||
// {
|
||||
// type: 'object',
|
||||
// required: ['extends', 'path'],
|
||||
// properties: {
|
||||
// extends: {
|
||||
// type: 'string',
|
||||
// description: nls.localize('contributes.notebook.renderer.entrypoint.extends', 'Existing renderer that this one extends.'),
|
||||
// },
|
||||
// path: {
|
||||
// type: 'string',
|
||||
// description: nls.localize('contributes.notebook.renderer.entrypoint', 'File to load in the webview to render the extension.'),
|
||||
// },
|
||||
// }
|
||||
// }
|
||||
]
|
||||
},
|
||||
[NotebookRendererContribution.hardDependencies]: {
|
||||
type: 'array',
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
.monaco-workbench .notebookOverlay .notebook-toolbar-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
display: none;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
@ -347,46 +347,6 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
/* top and bottom borders on cells */
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-top:before,
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-bottom:before,
|
||||
.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row .cell-inner-container:before,
|
||||
.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row .cell-inner-container:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-left:before,
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-right:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* top border */
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-top:before {
|
||||
border-top: 1px solid transparent;
|
||||
}
|
||||
|
||||
/* left border */
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-left:before {
|
||||
border-left: 1px solid transparent;
|
||||
}
|
||||
|
||||
/* bottom border */
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-bottom:before {
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
|
||||
/* right border */
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-right:before {
|
||||
border-right: 1px solid transparent;
|
||||
}
|
||||
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-top:before {
|
||||
top: 0;
|
||||
}
|
||||
|
|
|
@ -255,7 +255,7 @@ class CellContentProvider implements ITextModelContentProvider {
|
|||
}
|
||||
|
||||
if (result) {
|
||||
const once = result.onWillDispose(() => {
|
||||
const once = Event.any(result.onWillDispose, ref.object.notebook.onWillDispose)(() => {
|
||||
once.dispose();
|
||||
ref.dispose();
|
||||
});
|
||||
|
|
|
@ -23,6 +23,7 @@ import { INotebookEditor, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebo
|
|||
import { NotebooKernelActionViewItem } from 'vs/workbench/contrib/notebook/browser/notebookKernelActionViewItem';
|
||||
import { ActionViewWithLabel } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellActionView';
|
||||
import { CellMenus } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellMenus';
|
||||
import { ExperimentalGlobalToolbar } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService';
|
||||
|
||||
|
@ -97,10 +98,10 @@ export class NotebookEditorToolbar extends Disposable {
|
|||
this._notebookGlobalActionsMenu = this._register(cellMenu.getNotebookToolbar(this.contextKeyService));
|
||||
this._register(this._notebookGlobalActionsMenu);
|
||||
|
||||
this._useGlobalToolbar = this.configurationService.getValue<boolean | undefined>('notebook.experimental.globalToolbar') ?? false;
|
||||
this._useGlobalToolbar = this.configurationService.getValue<boolean | undefined>(ExperimentalGlobalToolbar) ?? false;
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('notebook.experimental.globalToolbar')) {
|
||||
this._useGlobalToolbar = this.configurationService.getValue<boolean>('notebook.experimental.globalToolbar');
|
||||
if (e.affectsConfiguration(ExperimentalGlobalToolbar)) {
|
||||
this._useGlobalToolbar = this.configurationService.getValue<boolean>(ExperimentalGlobalToolbar);
|
||||
this._showNotebookActionsinEditorToolbar();
|
||||
}
|
||||
}));
|
||||
|
|
|
@ -70,7 +70,6 @@ import { readFontInfo } from 'vs/editor/browser/config/configuration';
|
|||
import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
|
||||
import { NotebookEditorContextKeys } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidgetContextKeys';
|
||||
import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions';
|
||||
import { SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants';
|
||||
import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext';
|
||||
import { NotebookEditorToolbar } from 'vs/workbench/contrib/notebook/browser/notebookEditorToolbar';
|
||||
|
||||
|
@ -213,7 +212,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
private _dndController: CellDragAndDropController | null = null;
|
||||
private _listTopCellToolbar: ListTopCellToolbar | null = null;
|
||||
private _renderedEditors: Map<ICellViewModel, ICodeEditor | undefined> = new Map();
|
||||
private _viewContext: ViewContext | undefined;
|
||||
private _viewContext: ViewContext;
|
||||
private _notebookViewModel: NotebookViewModel | undefined;
|
||||
private _localStore: DisposableStore = this._register(new DisposableStore());
|
||||
private _localCellStateListeners: DisposableStore[] = [];
|
||||
|
@ -334,6 +333,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
this.useRenderer = !isWeb && !!this.configurationService.getValue<boolean>(ExperimentalUseMarkdownRenderer) && !accessibilityService.isScreenReaderOptimized();
|
||||
this._notebookOptions = new NotebookOptions(this.configurationService);
|
||||
this._register(this._notebookOptions);
|
||||
this._viewContext = new ViewContext(this._notebookOptions, new NotebookEventDispatcher());
|
||||
|
||||
this._overlayContainer = document.createElement('div');
|
||||
this.scopedContextKeyService = contextKeyService.createScoped(this._overlayContainer);
|
||||
|
@ -367,11 +367,15 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
this._updateForNotebookConfiguration();
|
||||
}
|
||||
|
||||
if (e.compactView) {
|
||||
if (e.compactView || e.focusIndicator || e.insertToolbarPosition) {
|
||||
this._styleElement?.remove();
|
||||
this._createLayoutStyles();
|
||||
this._webview?.updateOptions(this.notebookOptions.computeWebviewOptions());
|
||||
}
|
||||
|
||||
if (this._dimension && this._isVisible) {
|
||||
this.layout(this._dimension);
|
||||
}
|
||||
}));
|
||||
|
||||
this.notebookEditorService.addNotebookEditor(this);
|
||||
|
@ -552,10 +556,12 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
codeCellLeftMargin,
|
||||
markdownCellBottomMargin,
|
||||
markdownCellTopMargin,
|
||||
bottomCellToolbarGap,
|
||||
bottomCellToolbarHeight,
|
||||
bottomToolbarGap: bottomCellToolbarGap,
|
||||
bottomToolbarHeight: bottomCellToolbarHeight,
|
||||
collapsedIndicatorHeight,
|
||||
compactView
|
||||
compactView,
|
||||
focusIndicator,
|
||||
insertToolbarPosition
|
||||
} = this._notebookOptions.getLayoutConfiguration();
|
||||
|
||||
const styleSheets: string[] = [];
|
||||
|
@ -566,6 +572,103 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row div.cell.code { margin-left: ${codeCellLeftMargin}px; }`);
|
||||
}
|
||||
|
||||
// focus indicator
|
||||
if (focusIndicator === 'border') {
|
||||
styleSheets.push(`
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-top:before,
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-bottom:before,
|
||||
.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row .cell-inner-container:before,
|
||||
.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row .cell-inner-container:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-left:before,
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-right:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* top border */
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-top:before {
|
||||
border-top: 1px solid transparent;
|
||||
}
|
||||
|
||||
/* left border */
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-left:before {
|
||||
border-left: 1px solid transparent;
|
||||
}
|
||||
|
||||
/* bottom border */
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-bottom:before {
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
|
||||
/* right border */
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-right:before {
|
||||
border-right: 1px solid transparent;
|
||||
}
|
||||
`);
|
||||
|
||||
// left and right border margins
|
||||
styleSheets.push(`
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row.focused .cell-focus-indicator-left:before,
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row.focused .cell-focus-indicator-right:before,
|
||||
.monaco-workbench .notebookOverlay .monaco-list.selection-multiple .monaco-list-row.code-cell-row.selected .cell-focus-indicator-left:before,
|
||||
.monaco-workbench .notebookOverlay .monaco-list.selection-multiple .monaco-list-row.code-cell-row.selected .cell-focus-indicator-right:before {
|
||||
top: -${cellTopMargin}px; height: calc(100% + ${cellTopMargin + cellBottomMargin}px)
|
||||
}`);
|
||||
} else {
|
||||
styleSheets.push(`
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-left:before,
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-right:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 0px;
|
||||
height: 100%;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-left:before {
|
||||
border-left: 1px solid transparent;
|
||||
border-right: 1px solid transparent;
|
||||
border-radius: 2px;
|
||||
}
|
||||
`);
|
||||
|
||||
// left and right border margins
|
||||
styleSheets.push(`
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row.focused .cell-focus-indicator-left:before,
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row.focused .cell-focus-indicator-right:before,
|
||||
.monaco-workbench .notebookOverlay .monaco-list.selection-multiple .monaco-list-row.code-cell-row.selected .cell-focus-indicator-left:before,
|
||||
.monaco-workbench .notebookOverlay .monaco-list.selection-multiple .monaco-list-row.code-cell-row.selected .cell-focus-indicator-right:before {
|
||||
top: 0px; height: 100%px;
|
||||
}`);
|
||||
}
|
||||
|
||||
// between cell insert toolbar
|
||||
if (insertToolbarPosition === 'betweenCells' || insertToolbarPosition === 'both') {
|
||||
styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container { display: flex; }`);
|
||||
styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .cell-list-top-cell-toolbar-container { display: flex; }`);
|
||||
} else {
|
||||
styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container { display: none; }`);
|
||||
styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .cell-list-top-cell-toolbar-container { display: none; }`);
|
||||
}
|
||||
|
||||
// top insert toolbar
|
||||
const topInsertToolbarHeight = this._notebookOptions.computeTopInserToolbarHeight();
|
||||
styleSheets.push(`.notebookOverlay .cell-list-top-cell-toolbar-container { top: -${topInsertToolbarHeight}px }`);
|
||||
styleSheets.push(`.notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element,
|
||||
.notebookOverlay > .cell-list-container > .notebook-gutter > .monaco-list > .monaco-scrollable-element {
|
||||
padding-top: ${topInsertToolbarHeight}px;
|
||||
box-sizing: border-box;
|
||||
}`);
|
||||
|
||||
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .code-cell-row div.cell.code { margin-left: ${codeCellLeftMargin + cellRunGutter}px; }`);
|
||||
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row div.cell { margin-right: ${cellRightMargin}px; }`);
|
||||
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > .cell-inner-container { padding-top: ${cellTopMargin}px; }`);
|
||||
|
@ -591,7 +694,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
styleSheets.push(`.notebookOverlay .monaco-list .monaco-list-row .cell-shadow-container-bottom { top: ${cellBottomMargin}px; }`);
|
||||
|
||||
styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-collapsed-part { margin-left: ${codeCellLeftMargin + cellRunGutter}px; height: ${collapsedIndicatorHeight}px; }`);
|
||||
styleSheets.push(`.notebookOverlay .cell-list-top-cell-toolbar-container { top: -${SCROLLABLE_ELEMENT_PADDING_TOP}px }`);
|
||||
|
||||
styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container { height: ${bottomCellToolbarHeight}px }`);
|
||||
styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .cell-list-top-cell-toolbar-container { height: ${bottomCellToolbarHeight}px }`);
|
||||
|
@ -607,15 +709,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
display: none;
|
||||
}`);
|
||||
|
||||
// left and right border margins
|
||||
styleSheets.push(`
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row.focused .cell-focus-indicator-left:before,
|
||||
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row.focused .cell-focus-indicator-right:before,
|
||||
.monaco-workbench .notebookOverlay .monaco-list.selection-multiple .monaco-list-row.code-cell-row.selected .cell-focus-indicator-left:before,
|
||||
.monaco-workbench .notebookOverlay .monaco-list.selection-multiple .monaco-list-row.code-cell-row.selected .cell-focus-indicator-right:before {
|
||||
top: -${cellTopMargin}px; height: calc(100% + ${cellTopMargin + cellBottomMargin}px)
|
||||
}`);
|
||||
|
||||
this._styleElement.textContent = styleSheets.join('\n');
|
||||
}
|
||||
|
||||
|
@ -641,6 +734,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
'NotebookCellList',
|
||||
this._overlayContainer,
|
||||
this._body,
|
||||
this._viewContext,
|
||||
this._listDelegate,
|
||||
renderers,
|
||||
this.scopedContextKeyService,
|
||||
|
@ -1023,8 +1117,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
|
||||
private async _attachModel(textModel: NotebookTextModel, viewState: INotebookEditorViewState | undefined) {
|
||||
await this._createWebview(this.getId(), textModel.uri);
|
||||
|
||||
this._viewContext = new ViewContext(this._notebookOptions, new NotebookEventDispatcher());
|
||||
this.viewModel = this.instantiationService.createInstance(NotebookViewModel, textModel.viewType, textModel, this._viewContext, this.getLayoutInfo());
|
||||
this._viewContext.eventDispatcher.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]);
|
||||
|
||||
|
@ -1125,7 +1217,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
}));
|
||||
|
||||
if (this._dimension) {
|
||||
this._list.layout(this._dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP, this._dimension.width);
|
||||
const topInserToolbarHeight = this._notebookOptions.computeTopInserToolbarHeight();
|
||||
this._list.layout(this._dimension.height - topInserToolbarHeight, this._dimension.width);
|
||||
} else {
|
||||
this._list.layout();
|
||||
}
|
||||
|
@ -1353,16 +1446,18 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
};
|
||||
}
|
||||
|
||||
const topInserToolbarHeight = this._notebookOptions.computeTopInserToolbarHeight();
|
||||
|
||||
this._dimension = new DOM.Dimension(dimension.width, dimension.height);
|
||||
DOM.size(this._body, dimension.width, dimension.height - (this._notebookTopToolbar?.useGlobalToolbar ? /** Toolbar height */ 26 : 0));
|
||||
if (this._list.getRenderHeight() < dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP) {
|
||||
if (this._list.getRenderHeight() < dimension.height - topInserToolbarHeight) {
|
||||
// the new dimension is larger than the list viewport, update its additional height first, otherwise the list view will move down a bit (as the `scrollBottom` will move down)
|
||||
this._list.updateOptions({ additionalScrollHeight: this._scrollBeyondLastLine ? Math.max(0, (dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP - 50)) : SCROLLABLE_ELEMENT_PADDING_TOP });
|
||||
this._list.layout(dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP, dimension.width);
|
||||
this._list.updateOptions({ additionalScrollHeight: this._scrollBeyondLastLine ? Math.max(0, (dimension.height - topInserToolbarHeight - 50)) : topInserToolbarHeight });
|
||||
this._list.layout(dimension.height - topInserToolbarHeight, dimension.width);
|
||||
} else {
|
||||
// the new dimension is smaller than the list viewport, if we update the additional height, the `scrollBottom` will move up, which moves the whole list view upwards a bit. So we run a layout first.
|
||||
this._list.layout(dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP, dimension.width);
|
||||
this._list.updateOptions({ additionalScrollHeight: this._scrollBeyondLastLine ? Math.max(0, (dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP - 50)) : SCROLLABLE_ELEMENT_PADDING_TOP });
|
||||
this._list.layout(dimension.height - topInserToolbarHeight, dimension.width);
|
||||
this._list.updateOptions({ additionalScrollHeight: this._scrollBeyondLastLine ? Math.max(0, (dimension.height - topInserToolbarHeight - 50)) : topInserToolbarHeight });
|
||||
}
|
||||
|
||||
this._overlayContainer.style.visibility = 'visible';
|
||||
|
@ -2334,8 +2429,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
const cell = this.getCellById(cellId);
|
||||
const layoutConfiguration = this._notebookOptions.getLayoutConfiguration();
|
||||
if (cell && cell instanceof MarkdownCellViewModel) {
|
||||
if (height + layoutConfiguration.bottomCellToolbarGap !== cell.layoutInfo.totalHeight) {
|
||||
this._debug('updateMarkdownCellHeight', cell.handle, height + layoutConfiguration.bottomCellToolbarGap, isInit);
|
||||
if (height + layoutConfiguration.bottomToolbarGap !== cell.layoutInfo.totalHeight) {
|
||||
this._debug('updateMarkdownCellHeight', cell.handle, height + layoutConfiguration.bottomToolbarGap, isInit);
|
||||
cell.renderedMarkdownHeight = height;
|
||||
}
|
||||
}
|
||||
|
@ -2409,7 +2504,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
this._webviewTransparentCover = null;
|
||||
this._dndController = null;
|
||||
this._listTopCellToolbar = null;
|
||||
this._viewContext = undefined;
|
||||
this._notebookViewModel = undefined;
|
||||
this._cellContextKeyManager = null;
|
||||
this._renderedEditors.clear();
|
||||
|
@ -2555,12 +2649,6 @@ export const cellEditorBackground = registerColor('notebook.cellEditorBackground
|
|||
}, nls.localize('notebook.cellEditorBackground', "Cell editor background color."));
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
collector.addRule(`.notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element,
|
||||
.notebookOverlay > .cell-list-container > .notebook-gutter > .monaco-list > .monaco-scrollable-element {
|
||||
padding-top: ${SCROLLABLE_ELEMENT_PADDING_TOP}px;
|
||||
box-sizing: border-box;
|
||||
}`);
|
||||
|
||||
const link = theme.getColor(textLinkForeground);
|
||||
if (link) {
|
||||
collector.addRule(`.notebookOverlay .output a,
|
||||
|
@ -2628,8 +2716,8 @@ registerThemingParticipant((theme, collector) => {
|
|||
|
||||
const focusedCellBackgroundColor = theme.getColor(focusedCellBackground);
|
||||
if (focusedCellBackgroundColor) {
|
||||
collector.addRule(`.notebookOverlay .code-cell-row.focused .cell-focus-indicator,
|
||||
.notebookOverlay .markdown-cell-row.focused { background-color: ${focusedCellBackgroundColor} !important; }`);
|
||||
collector.addRule(`.notebookOverlay .code-cell-row.focused .cell-focus-indicator { background-color: ${focusedCellBackgroundColor} !important; }`);
|
||||
collector.addRule(`.notebookOverlay .markdown-cell-row.focused { background-color: ${focusedCellBackgroundColor} !important; }`);
|
||||
collector.addRule(`.notebookOverlay .code-cell-row.focused .cell-collapsed-part { background-color: ${focusedCellBackgroundColor} !important; }`);
|
||||
}
|
||||
|
||||
|
|
|
@ -24,8 +24,8 @@ import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/
|
|||
import { diff, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, CellKind, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { ICellRange, cellRangesToIndexes } from 'vs/workbench/contrib/notebook/common/notebookRange';
|
||||
import { clamp } from 'vs/base/common/numbers';
|
||||
import { SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants';
|
||||
import { ISplice } from 'vs/base/common/sequence';
|
||||
import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext';
|
||||
|
||||
export interface IFocusNextPreviousDelegate {
|
||||
onFocusNext(applyFocusNext: () => void): void;
|
||||
|
@ -91,10 +91,13 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
|
|||
|
||||
private readonly _focusNextPreviousDelegate: IFocusNextPreviousDelegate;
|
||||
|
||||
private readonly _viewContext: ViewContext;
|
||||
|
||||
constructor(
|
||||
private listUser: string,
|
||||
parentContainer: HTMLElement,
|
||||
container: HTMLElement,
|
||||
viewContext: ViewContext,
|
||||
delegate: IListVirtualDelegate<CellViewModel>,
|
||||
renderers: IListRenderer<CellViewModel, BaseCellRenderTemplate>[],
|
||||
contextKeyService: IContextKeyService,
|
||||
|
@ -106,6 +109,7 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
|
|||
) {
|
||||
super(listUser, container, delegate, renderers, options, contextKeyService, listService, themeService, configurationService, keybindingService);
|
||||
NOTEBOOK_CELL_LIST_FOCUSED.bindTo(this.contextKeyService).set(true);
|
||||
this._viewContext = viewContext;
|
||||
this._focusNextPreviousDelegate = options.focusNextPreviousDelegate;
|
||||
this._previousFocusedElements = this.getFocusedElements();
|
||||
this._localDisposableStore.add(this.onDidChangeFocus((e) => {
|
||||
|
@ -900,7 +904,8 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
|
|||
}
|
||||
|
||||
getViewScrollBottom() {
|
||||
return this.getViewScrollTop() + this.view.renderHeight - SCROLLABLE_ELEMENT_PADDING_TOP;
|
||||
const topInsertToolbarHeight = this._viewContext.notebookOptions.computeTopInserToolbarHeight();
|
||||
return this.getViewScrollTop() + this.view.renderHeight - topInsertToolbarHeight;
|
||||
}
|
||||
|
||||
private _revealRange(viewIndex: number, range: Range, revealType: CellRevealType, newlyCreated: boolean, alignToBottom: boolean) {
|
||||
|
|
|
@ -26,10 +26,10 @@ 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 { preloadsScriptStr, WebviewPreloadRenderer } 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, NotebookRendererMatch } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { INotebookKernel, 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';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
|
@ -197,7 +197,7 @@ export interface ICreationRequestMessage {
|
|||
cellTop: number;
|
||||
outputOffset: number;
|
||||
left: number;
|
||||
requiredPreloads: ReadonlyArray<IPreloadResource>;
|
||||
requiredPreloads: ReadonlyArray<IControllerPreload>;
|
||||
readonly initiallyHidden?: boolean;
|
||||
rendererId?: string | undefined;
|
||||
}
|
||||
|
@ -257,17 +257,15 @@ export interface IAckOutputHeightMessage {
|
|||
height: number;
|
||||
}
|
||||
|
||||
export type PreloadSource = 'kernel' | { rendererId: string };
|
||||
|
||||
export interface IPreloadResource {
|
||||
export interface IControllerPreload {
|
||||
originalUri: string;
|
||||
uri: string;
|
||||
source: PreloadSource;
|
||||
}
|
||||
|
||||
export interface IUpdatePreloadResourceMessage {
|
||||
export interface IUpdateControllerPreloadsMessage {
|
||||
type: 'preload';
|
||||
resources: IPreloadResource[];
|
||||
resources: IControllerPreload[];
|
||||
}
|
||||
|
||||
export interface IUpdateDecorationsMessage {
|
||||
|
@ -370,7 +368,7 @@ export type ToWebviewMessage =
|
|||
| IClearOutputRequestMessage
|
||||
| IHideOutputMessage
|
||||
| IShowOutputMessage
|
||||
| IUpdatePreloadResourceMessage
|
||||
| IUpdateControllerPreloadsMessage
|
||||
| IUpdateDecorationsMessage
|
||||
| ICustomKernelMessage
|
||||
| ICreateMarkdownMessage
|
||||
|
@ -490,7 +488,7 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
|
|||
}
|
||||
|
||||
private generateContent(coreDependencies: string, baseUrl: string) {
|
||||
const markupRenderer = this.getMarkdownRenderer();
|
||||
const renderersData = this.getRendererData();
|
||||
return html`
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
@ -749,36 +747,19 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
|
|||
</script>
|
||||
${coreDependencies}
|
||||
<div id='container' class="widgetarea" style="position: absolute;width:100%;top: 0px"></div>
|
||||
<script type="module">${preloadsScriptStr(this.options, markupRenderer)}</script>
|
||||
<script type="module">${preloadsScriptStr(this.options, renderersData)}</script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
private getMarkdownRenderer(): WebviewPreloadRenderer[] {
|
||||
const markdownMimeType = 'text/markdown';
|
||||
const allRenderers = this.notebookService.getRenderers()
|
||||
.filter(renderer => renderer.matchesWithoutKernel(markdownMimeType) !== NotebookRendererMatch.Never);
|
||||
|
||||
const topLevelMarkdownRenderers = allRenderers
|
||||
.filter(renderer => renderer.dependencies.length === 0);
|
||||
|
||||
const subRenderers = new Map<string, Array<{ entrypoint: string }>>();
|
||||
for (const renderer of allRenderers) {
|
||||
for (const dep of renderer.dependencies) {
|
||||
if (!subRenderers.has(dep)) {
|
||||
subRenderers.set(dep, []);
|
||||
}
|
||||
const entryPoint = this.asWebviewUri(renderer.entrypoint, renderer.extensionLocation);
|
||||
subRenderers.get(dep)!.push({ entrypoint: entryPoint.toString(true) });
|
||||
}
|
||||
}
|
||||
|
||||
return topLevelMarkdownRenderers.map((renderer): WebviewPreloadRenderer => {
|
||||
const src = this.asWebviewUri(renderer.entrypoint, renderer.extensionLocation);
|
||||
private getRendererData(): RendererMetadata[] {
|
||||
return this.notebookService.getRenderers().map((renderer): RendererMetadata => {
|
||||
const entrypoint = this.asWebviewUri(renderer.entrypoint, renderer.extensionLocation).toString();
|
||||
return {
|
||||
entrypoint: src.toString(),
|
||||
id: renderer.id,
|
||||
entrypoint,
|
||||
mimeTypes: renderer.mimeTypes,
|
||||
dependencies: subRenderers.get(renderer.id) || [],
|
||||
extends: renderer.extends,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -1205,7 +1186,6 @@ var requirejs = (function() {
|
|||
if (this._currentKernel) {
|
||||
this._updatePreloadsFromKernel(this._currentKernel);
|
||||
}
|
||||
this.updateRendererPreloads(renderers);
|
||||
|
||||
for (const [output, inset] of this.insetMapping.entries()) {
|
||||
this._sendMessageToWebview({ ...inset.cachedCreation, initiallyHidden: this.hiddenInsetMapping.has(output) });
|
||||
|
@ -1480,7 +1460,6 @@ var requirejs = (function() {
|
|||
...messageBase,
|
||||
outputId: output.outputId,
|
||||
rendererId: content.renderer.id,
|
||||
requiredPreloads: await this.updateRendererPreloads([content.renderer]),
|
||||
content: {
|
||||
type: RenderOutputType.Extension,
|
||||
outputId: output.outputId,
|
||||
|
@ -1611,13 +1590,13 @@ var requirejs = (function() {
|
|||
}
|
||||
|
||||
private _updatePreloadsFromKernel(kernel: INotebookKernel) {
|
||||
const resources: IPreloadResource[] = [];
|
||||
const resources: IControllerPreload[] = [];
|
||||
for (const preload of kernel.preloadUris) {
|
||||
const uri = this.environmentService.isExtensionDevelopment && (preload.scheme === 'http' || preload.scheme === 'https')
|
||||
? preload : this.asWebviewUri(preload, undefined);
|
||||
|
||||
if (!this._preloadsCache.has(uri.toString())) {
|
||||
resources.push({ uri: uri.toString(), originalUri: preload.toString(), source: 'kernel' });
|
||||
resources.push({ uri: uri.toString(), originalUri: preload.toString() });
|
||||
this._preloadsCache.add(uri.toString());
|
||||
}
|
||||
}
|
||||
|
@ -1629,43 +1608,7 @@ var requirejs = (function() {
|
|||
this._updatePreloads(resources);
|
||||
}
|
||||
|
||||
async updateRendererPreloads(renderers: Iterable<INotebookRendererInfo>) {
|
||||
if (this._disposed) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const requiredPreloads: IPreloadResource[] = [];
|
||||
const resources: IPreloadResource[] = [];
|
||||
const extensionLocations: URI[] = [];
|
||||
for (const rendererInfo of renderers) {
|
||||
extensionLocations.push(rendererInfo.extensionLocation);
|
||||
for (const preload of [rendererInfo.entrypoint, ...rendererInfo.preloads]) {
|
||||
const uri = this.asWebviewUri(preload, rendererInfo.extensionLocation);
|
||||
const resource: IPreloadResource = {
|
||||
uri: uri.toString(),
|
||||
originalUri: preload.toString(),
|
||||
source: { rendererId: rendererInfo.id },
|
||||
};
|
||||
|
||||
requiredPreloads.push(resource);
|
||||
|
||||
if (!this._preloadsCache.has(uri.toString())) {
|
||||
resources.push(resource);
|
||||
this._preloadsCache.add(uri.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!resources.length) {
|
||||
return requiredPreloads;
|
||||
}
|
||||
|
||||
this.rendererRootsCache = extensionLocations;
|
||||
this._updatePreloads(resources);
|
||||
return requiredPreloads;
|
||||
}
|
||||
|
||||
private _updatePreloads(resources: IPreloadResource[]) {
|
||||
private _updatePreloads(resources: IControllerPreload[]) {
|
||||
if (!this.webview) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -157,7 +157,7 @@ export class CellDragAndDropController extends Disposable {
|
|||
|
||||
private updateInsertIndicator(dropDirection: string, insertionIndicatorAbsolutePos: number) {
|
||||
const layoutInfo = this.notebookEditor.notebookOptions.getLayoutConfiguration();
|
||||
const insertionIndicatorTop = insertionIndicatorAbsolutePos - this.list.scrollTop + layoutInfo.bottomCellToolbarGap / 2;
|
||||
const insertionIndicatorTop = insertionIndicatorAbsolutePos - this.list.scrollTop + layoutInfo.bottomToolbarGap / 2;
|
||||
if (insertionIndicatorTop >= 0) {
|
||||
this.listInsertionIndicator.style.top = `${insertionIndicatorTop}px`;
|
||||
this.setInsertIndicatorVisibility(true);
|
||||
|
@ -200,7 +200,7 @@ export class CellDragAndDropController extends Disposable {
|
|||
const cellHeight = this.list.elementHeight(draggedOverCell);
|
||||
const insertionIndicatorAbsolutePos = dropDirection === 'above' ? cellTop : cellTop + cellHeight;
|
||||
const layoutInfo = this.notebookEditor.notebookOptions.getLayoutConfiguration();
|
||||
const insertionIndicatorTop = insertionIndicatorAbsolutePos - this.list.scrollTop + layoutInfo.bottomCellToolbarGap / 2;
|
||||
const insertionIndicatorTop = insertionIndicatorAbsolutePos - this.list.scrollTop + layoutInfo.bottomToolbarGap / 2;
|
||||
const editorHeight = this.notebookEditor.getDomNode().getBoundingClientRect().height;
|
||||
if (insertionIndicatorTop < 0 || insertionIndicatorTop > editorHeight) {
|
||||
// Ignore drop, insertion point is off-screen
|
||||
|
|
|
@ -881,10 +881,10 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende
|
|||
const layoutInfo = this.notebookEditor.notebookOptions.getLayoutConfiguration();
|
||||
templateData.focusIndicatorLeft.style.height = `${element.layoutInfo.indicatorHeight}px`;
|
||||
templateData.focusIndicatorRight.style.height = `${element.layoutInfo.indicatorHeight}px`;
|
||||
templateData.focusIndicatorBottom.style.top = `${element.layoutInfo.totalHeight - layoutInfo.bottomCellToolbarGap - layoutInfo.cellBottomMargin}px`;
|
||||
templateData.focusIndicatorBottom.style.top = `${element.layoutInfo.totalHeight - layoutInfo.bottomToolbarGap - layoutInfo.cellBottomMargin}px`;
|
||||
templateData.outputContainer.style.top = `${element.layoutInfo.outputContainerOffset}px`;
|
||||
templateData.outputShowMoreContainer.style.top = `${element.layoutInfo.outputShowMoreContainerOffset}px`;
|
||||
templateData.dragHandle.style.height = `${element.layoutInfo.totalHeight - layoutInfo.bottomCellToolbarGap}px`;
|
||||
templateData.dragHandle.style.height = `${element.layoutInfo.totalHeight - layoutInfo.bottomToolbarGap}px`;
|
||||
}
|
||||
|
||||
renderElement(element: CodeCellViewModel, index: number, templateData: CodeCellRenderTemplate, height: number | undefined): void {
|
||||
|
|
|
@ -41,7 +41,7 @@ interface PreloadStyles {
|
|||
|
||||
declare function __import(path: string): Promise<any>;
|
||||
|
||||
async function webviewPreloads(style: PreloadStyles, rendererData: readonly WebviewPreloadRenderer[]) {
|
||||
async function webviewPreloads(style: PreloadStyles, rendererData: readonly RendererMetadata[]) {
|
||||
const acquireVsCodeApi = globalThis.acquireVsCodeApi;
|
||||
const vscode = acquireVsCodeApi();
|
||||
delete (globalThis as any).acquireVsCodeApi;
|
||||
|
@ -111,32 +111,94 @@ async function webviewPreloads(style: PreloadStyles, rendererData: readonly Webv
|
|||
}
|
||||
};
|
||||
|
||||
const runScript = async (url: string, originalUri: string, globals: { [name: string]: unknown } = {}): Promise<() => (PreloadResult)> => {
|
||||
let text: string;
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
text = await res.text();
|
||||
if (!res.ok) {
|
||||
throw new Error(`Unexpected ${res.status} requesting ${originalUri}: ${text || res.statusText}`);
|
||||
}
|
||||
|
||||
globals.scriptUrl = url;
|
||||
} catch (e) {
|
||||
return () => ({ state: PreloadState.Error, error: e.message });
|
||||
async function loadScriptSource(url: string, originalUri = url): Promise<string> {
|
||||
const res = await fetch(url);
|
||||
const text = await res.text();
|
||||
if (!res.ok) {
|
||||
throw new Error(`Unexpected ${res.status} requesting ${originalUri}: ${text || res.statusText}`);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
interface RendererContext {
|
||||
getState<T>(): T | undefined;
|
||||
setState<T>(newState: T): void;
|
||||
|
||||
getRenderer(id: string): any | undefined;
|
||||
}
|
||||
|
||||
function createRendererContext(rendererId: string): RendererContext {
|
||||
return {
|
||||
setState: newState => vscode.setState({ ...vscode.getState(), [rendererId]: newState }),
|
||||
getState: <T>() => {
|
||||
const state = vscode.getState();
|
||||
return typeof state === 'object' && state ? state[rendererId] as T : undefined;
|
||||
},
|
||||
getRenderer: (id: string) => renderers.getRenderer(id),
|
||||
};
|
||||
}
|
||||
|
||||
interface ScriptModule {
|
||||
activate: (ctx?: RendererContext) => any;
|
||||
}
|
||||
|
||||
const invokeSourceWithGlobals = (functionSrc: string, globals: { [name: string]: unknown }) => {
|
||||
const args = Object.entries(globals);
|
||||
return () => {
|
||||
try {
|
||||
new Function(...args.map(([k]) => k), text)(...args.map(([, v]) => v));
|
||||
return { state: PreloadState.Ok };
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return { state: PreloadState.Error, error: e.message };
|
||||
return new Function(...args.map(([k]) => k), functionSrc)(...args.map(([, v]) => v));
|
||||
};
|
||||
|
||||
const runPreload = async (url: string, originalUri: string): Promise<ScriptModule> => {
|
||||
const text = await loadScriptSource(url, originalUri);
|
||||
return {
|
||||
activate: () => {
|
||||
return invokeSourceWithGlobals(text, kernelPreloadGlobals);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const runRenderScript = async (url: string, rendererId: string): Promise<ScriptModule> => {
|
||||
const text = await loadScriptSource(url);
|
||||
// TODO: Support both the new module based renderers and the old style global renderers
|
||||
const isModule = /\bexport\b.*\bactivate\b/.test(text);
|
||||
if (isModule) {
|
||||
return __import(url);
|
||||
} else {
|
||||
return createBackCompatModule(rendererId, url, text);
|
||||
}
|
||||
};
|
||||
|
||||
const createBackCompatModule = (rendererId: string, scriptUrl: string, scriptText: string): ScriptModule => ({
|
||||
activate: (): RendererApi => {
|
||||
const onDidCreateOutput = createEmitter<ICreateCellInfo>();
|
||||
const onWillDestroyOutput = createEmitter<undefined | IDestroyCellInfo>();
|
||||
|
||||
const globals = {
|
||||
scriptUrl,
|
||||
acquireNotebookRendererApi: <T>(): GlobalNotebookRendererApi<T> => ({
|
||||
onDidCreateOutput: onDidCreateOutput.event,
|
||||
onWillDestroyOutput: onWillDestroyOutput.event,
|
||||
setState: newState => vscode.setState({ ...vscode.getState(), [rendererId]: newState }),
|
||||
getState: () => {
|
||||
const state = vscode.getState();
|
||||
return typeof state === 'object' && state ? state[rendererId] as T : undefined;
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
invokeSourceWithGlobals(scriptText, globals);
|
||||
|
||||
return {
|
||||
renderCell(id, context) {
|
||||
onDidCreateOutput.fire({ ...context, outputId: id });
|
||||
},
|
||||
destroyCell(id) {
|
||||
onWillDestroyOutput.fire(id ? { outputId: id } : undefined);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const dimensionUpdater = new class {
|
||||
private readonly pending = new Map<string, DimensionUpdate>();
|
||||
|
||||
|
@ -352,8 +414,6 @@ async function webviewPreloads(style: PreloadStyles, rendererData: readonly Webv
|
|||
focusTrackers.set(outputId, new FocusTracker(element, outputId));
|
||||
}
|
||||
|
||||
const dontEmit = Symbol('dontEmit');
|
||||
|
||||
function createEmitter<T>(listenerChange: (listeners: Set<Listener<T>>) => void = () => undefined): EmitterLike<T> {
|
||||
const listeners = new Set<Listener<T>>();
|
||||
return {
|
||||
|
@ -385,29 +445,21 @@ async function webviewPreloads(style: PreloadStyles, rendererData: readonly Webv
|
|||
};
|
||||
}
|
||||
|
||||
// Maps the events in the given emitter, invoking mapFn on each one. mapFn can return
|
||||
// the dontEmit symbol to skip emission.
|
||||
function mapEmitter<T, R>(emitter: EmitterLike<T>, mapFn: (data: T) => R | typeof dontEmit) {
|
||||
let listener: IDisposable;
|
||||
const mapped = createEmitter(listeners => {
|
||||
if (listeners.size && !listener) {
|
||||
listener = emitter.event(data => {
|
||||
const v = mapFn(data);
|
||||
if (v !== dontEmit) {
|
||||
mapped.fire(v);
|
||||
}
|
||||
});
|
||||
} else if (listener && !listeners.size) {
|
||||
listener.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
return mapped.event;
|
||||
function showPreloadErrors(outputNode: HTMLElement, ...errors: readonly Error[]) {
|
||||
outputNode.innerText = `Error loading preloads:`;
|
||||
const errList = document.createElement('ul');
|
||||
for (const result of errors) {
|
||||
console.error(result);
|
||||
const item = document.createElement('li');
|
||||
item.innerText = result.message;
|
||||
errList.appendChild(item);
|
||||
}
|
||||
outputNode.appendChild(errList);
|
||||
}
|
||||
|
||||
interface ICreateCellInfo {
|
||||
element: HTMLElement;
|
||||
outputId: string;
|
||||
outputId?: string;
|
||||
|
||||
mime: string;
|
||||
value: unknown;
|
||||
|
@ -418,26 +470,15 @@ async function webviewPreloads(style: PreloadStyles, rendererData: readonly Webv
|
|||
outputId: string;
|
||||
}
|
||||
|
||||
const onWillDestroyOutput = createEmitter<'all' | { rendererId: string, info: IDestroyCellInfo }>();
|
||||
const onDidCreateOutput = createEmitter<{ rendererId: string, info: ICreateCellInfo }>();
|
||||
const onDidReceiveKernelMessage = createEmitter<unknown>();
|
||||
|
||||
const acquireNotebookRendererApi = <T>(id: string) => ({
|
||||
setState(newState: T) {
|
||||
vscode.setState({ ...vscode.getState(), [id]: newState });
|
||||
},
|
||||
getState(): T | undefined {
|
||||
const state = vscode.getState();
|
||||
return typeof state === 'object' && state ? state[id] as T : undefined;
|
||||
},
|
||||
onWillDestroyOutput: mapEmitter(onWillDestroyOutput, (evt) => {
|
||||
if (evt === 'all') {
|
||||
return undefined;
|
||||
}
|
||||
return evt.rendererId === id ? evt.info : dontEmit;
|
||||
}),
|
||||
onDidCreateOutput: mapEmitter(onDidCreateOutput, ({ rendererId, info }) => rendererId === id ? info : dontEmit),
|
||||
});
|
||||
/** @deprecated */
|
||||
interface GlobalNotebookRendererApi<T> {
|
||||
setState: (newState: T) => void;
|
||||
getState(): T | undefined;
|
||||
readonly onWillDestroyOutput: Event<undefined | IDestroyCellInfo>;
|
||||
readonly onDidCreateOutput: Event<ICreateCellInfo>;
|
||||
}
|
||||
|
||||
const kernelPreloadGlobals = {
|
||||
acquireVsCodeApi,
|
||||
|
@ -445,42 +486,6 @@ async function webviewPreloads(style: PreloadStyles, rendererData: readonly Webv
|
|||
postKernelMessage: (data: unknown) => postNotebookMessage('customKernelMessage', { message: data }),
|
||||
};
|
||||
|
||||
const enum PreloadState {
|
||||
Ok,
|
||||
Error
|
||||
}
|
||||
|
||||
type PreloadResult = { state: PreloadState.Ok } | { state: PreloadState.Error, error: string };
|
||||
|
||||
/**
|
||||
* Map of preload resource URIs to promises that resolve one the resource
|
||||
* loads or errors.
|
||||
*/
|
||||
const preloadPromises = new Map<string, Promise<PreloadResult>>();
|
||||
const queuedOuputActions = new Map<string, Promise<void>>();
|
||||
|
||||
/**
|
||||
* Enqueues an action that affects a output. This blocks behind renderer load
|
||||
* requests that affect the same output. This should be called whenever you
|
||||
* do something that affects output to ensure it runs in
|
||||
* the correct order.
|
||||
*/
|
||||
const enqueueOutputAction = <T extends { outputId: string; }>(event: T, fn: (event: T) => Promise<void> | void) => {
|
||||
const queued = queuedOuputActions.get(event.outputId);
|
||||
const maybePromise = queued ? queued.then(() => fn(event)) : fn(event);
|
||||
if (typeof maybePromise === 'undefined') {
|
||||
return; // a synchonrously-called function, we're done
|
||||
}
|
||||
|
||||
const promise = maybePromise.then(() => {
|
||||
if (queuedOuputActions.get(event.outputId) === promise) {
|
||||
queuedOuputActions.delete(event.outputId);
|
||||
}
|
||||
});
|
||||
|
||||
queuedOuputActions.set(event.outputId, promise);
|
||||
};
|
||||
|
||||
const ttPolicy = window.trustedTypes?.createPolicy('notebookOutputRenderer', {
|
||||
createHTML: value => value,
|
||||
createScript: value => value,
|
||||
|
@ -562,10 +567,15 @@ async function webviewPreloads(style: PreloadStyles, rendererData: readonly Webv
|
|||
}
|
||||
}
|
||||
break;
|
||||
case 'html':
|
||||
enqueueOutputAction(event.data, async data => {
|
||||
const preloadResults = await Promise.all(data.requiredPreloads.map(p => preloadPromises.get(p.uri)));
|
||||
if (!queuedOuputActions.has(data.outputId)) { // output was cleared while loading
|
||||
case 'html': {
|
||||
const data = event.data;
|
||||
outputs.enqueue(event.data.outputId, async (state) => {
|
||||
const preloadsAndErrors = await Promise.all<unknown>([
|
||||
data.rendererId ? renderers.load(data.rendererId) : undefined,
|
||||
...data.requiredPreloads.map(p => kernelPreloads.waitFor(p.uri)),
|
||||
].map(p => p?.catch(err => err)));
|
||||
|
||||
if (state.cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -615,37 +625,26 @@ async function webviewPreloads(style: PreloadStyles, rendererData: readonly Webv
|
|||
if (content.type === RenderOutputType.Html) {
|
||||
const trustedHtml = ttPolicy?.createHTML(content.htmlContent) ?? content.htmlContent;
|
||||
outputNode.innerHTML = trustedHtml as string;
|
||||
cellOutputContainer.appendChild(outputContainer);
|
||||
outputContainer.appendChild(outputNode);
|
||||
domEval(outputNode);
|
||||
} else if (preloadResults.some(e => e?.state === PreloadState.Error)) {
|
||||
outputNode.innerText = `Error loading preloads:`;
|
||||
const errList = document.createElement('ul');
|
||||
for (const result of preloadResults) {
|
||||
if (result?.state === PreloadState.Error) {
|
||||
const item = document.createElement('li');
|
||||
item.innerText = result.error;
|
||||
errList.appendChild(item);
|
||||
}
|
||||
}
|
||||
outputNode.appendChild(errList);
|
||||
cellOutputContainer.appendChild(outputContainer);
|
||||
outputContainer.appendChild(outputNode);
|
||||
} else if (preloadsAndErrors.some(e => e instanceof Error)) {
|
||||
const errors = preloadsAndErrors.filter((e): e is Error => e instanceof Error);
|
||||
showPreloadErrors(outputNode, ...errors);
|
||||
} else {
|
||||
onDidCreateOutput.fire({
|
||||
rendererId: data.rendererId!,
|
||||
info: {
|
||||
const rendererApi = preloadsAndErrors[0] as RendererApi;
|
||||
try {
|
||||
rendererApi.renderCell(outputId, {
|
||||
element: outputNode,
|
||||
outputId,
|
||||
mime: content.mimeType,
|
||||
value: content.value,
|
||||
metadata: content.metadata,
|
||||
}
|
||||
});
|
||||
cellOutputContainer.appendChild(outputContainer);
|
||||
outputContainer.appendChild(outputNode);
|
||||
});
|
||||
} catch (e) {
|
||||
showPreloadErrors(outputNode, e);
|
||||
}
|
||||
}
|
||||
|
||||
cellOutputContainer.appendChild(outputContainer);
|
||||
outputContainer.appendChild(outputNode);
|
||||
resizeObserver.observe(outputNode, outputId, true);
|
||||
|
||||
const clientHeight = outputNode.clientHeight;
|
||||
|
@ -670,6 +669,7 @@ async function webviewPreloads(style: PreloadStyles, rendererData: readonly Webv
|
|||
cellOutputContainer.style.visibility = data.initiallyHidden ? 'hidden' : 'visible';
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'view-scroll':
|
||||
{
|
||||
// const date = new Date();
|
||||
|
@ -696,8 +696,7 @@ async function webviewPreloads(style: PreloadStyles, rendererData: readonly Webv
|
|||
break;
|
||||
}
|
||||
case 'clear':
|
||||
queuedOuputActions.clear(); // stop all loading outputs
|
||||
onWillDestroyOutput.fire('all');
|
||||
renderers.clearAll();
|
||||
document.getElementById('container')!.innerText = '';
|
||||
|
||||
focusTrackers.forEach(ft => {
|
||||
|
@ -709,26 +708,29 @@ async function webviewPreloads(style: PreloadStyles, rendererData: readonly Webv
|
|||
const output = document.getElementById(event.data.outputId);
|
||||
const { rendererId, outputId } = event.data;
|
||||
|
||||
queuedOuputActions.delete(outputId); // stop any in-progress rendering
|
||||
outputs.cancelOutput(outputId);
|
||||
if (output && output.parentNode) {
|
||||
if (rendererId) {
|
||||
onWillDestroyOutput.fire({ rendererId, info: { outputId } });
|
||||
renderers.clearOutput(rendererId, outputId);
|
||||
}
|
||||
output.parentNode.removeChild(output);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'hideOutput':
|
||||
enqueueOutputAction(event.data, ({ outputId }) => {
|
||||
case 'hideOutput': {
|
||||
const { outputId } = event.data;
|
||||
outputs.enqueue(event.data.outputId, () => {
|
||||
const container = document.getElementById(outputId)?.parentElement?.parentElement;
|
||||
if (container) {
|
||||
container.style.visibility = 'hidden';
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'showOutput':
|
||||
enqueueOutputAction(event.data, ({ outputId, cellTop: top, }) => {
|
||||
}
|
||||
case 'showOutput': {
|
||||
const { outputId, cellTop: top } = event.data;
|
||||
outputs.enqueue(event.data.outputId, () => {
|
||||
const output = document.getElementById(outputId);
|
||||
if (output) {
|
||||
output.parentElement!.parentElement!.style.visibility = 'visible';
|
||||
|
@ -740,6 +742,7 @@ async function webviewPreloads(style: PreloadStyles, rendererData: readonly Webv
|
|||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'ack-dimension':
|
||||
{
|
||||
const { outputId, height } = event.data;
|
||||
|
@ -752,24 +755,8 @@ async function webviewPreloads(style: PreloadStyles, rendererData: readonly Webv
|
|||
}
|
||||
case 'preload':
|
||||
const resources = event.data.resources;
|
||||
let queue: Promise<PreloadResult> = Promise.resolve({ state: PreloadState.Ok });
|
||||
for (const { uri, originalUri, source } of resources) {
|
||||
const globals = source === 'kernel'
|
||||
? kernelPreloadGlobals
|
||||
: { acquireNotebookRendererApi: () => acquireNotebookRendererApi(source.rendererId) };
|
||||
|
||||
// create the promise so that the scripts download in parallel, but
|
||||
// only invoke them in series within the queue
|
||||
const promise = runScript(uri, originalUri, globals);
|
||||
queue = queue.then(() => promise.then(fn => {
|
||||
const result = fn();
|
||||
if (result.state === PreloadState.Error) {
|
||||
console.error(result.error);
|
||||
}
|
||||
|
||||
return result;
|
||||
}));
|
||||
preloadPromises.set(uri, queue);
|
||||
for (const { uri, originalUri } of resources) {
|
||||
kernelPreloads.load(uri, originalUri);
|
||||
}
|
||||
break;
|
||||
case 'focus-output':
|
||||
|
@ -806,51 +793,193 @@ async function webviewPreloads(style: PreloadStyles, rendererData: readonly Webv
|
|||
}
|
||||
});
|
||||
|
||||
interface MarkupRenderer {
|
||||
renderMarkup: (context: { element: HTMLElement, content: string }) => void;
|
||||
interface RendererApi {
|
||||
renderCell: (id: string, context: ICreateCellInfo) => void;
|
||||
destroyCell?: (id?: string) => void;
|
||||
}
|
||||
|
||||
const markupRenderers = new class {
|
||||
class Renderer {
|
||||
constructor(
|
||||
public readonly data: RendererMetadata,
|
||||
private readonly loadExtension: (id: string) => Promise<void>,
|
||||
) { }
|
||||
|
||||
private readonly mimeTypesToRenderers = new Map<string, {
|
||||
load: () => Promise<MarkupRenderer>;
|
||||
}>();
|
||||
private _loadPromise: Promise<RendererApi> | undefined;
|
||||
private _api: RendererApi | undefined;
|
||||
|
||||
public get api() { return this._api; }
|
||||
|
||||
public load(): Promise<RendererApi | undefined> {
|
||||
if (!this._loadPromise) {
|
||||
this._loadPromise = this._load();
|
||||
}
|
||||
|
||||
return this._loadPromise;
|
||||
}
|
||||
|
||||
/** Inner function cached in the _loadPromise(). */
|
||||
private async _load() {
|
||||
const module = await runRenderScript(this.data.entrypoint, this.data.id);
|
||||
if (!module) {
|
||||
return;
|
||||
}
|
||||
|
||||
const api = module.activate(createRendererContext(this.data.id));
|
||||
this._api = api;
|
||||
|
||||
// Squash any errors extends errors. They won't prevent the renderer
|
||||
// itself from working, so just log them.
|
||||
await Promise.all(rendererData
|
||||
.filter(d => d.extends === this.data.id)
|
||||
.map(d => this.loadExtension(d.id).catch(console.error)),
|
||||
);
|
||||
|
||||
return api;
|
||||
}
|
||||
}
|
||||
|
||||
const kernelPreloads = new class {
|
||||
private readonly preloads = new Map<string /* uri */, Promise<ScriptModule>>();
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves when the given preload is activated.
|
||||
*/
|
||||
public waitFor(uri: string) {
|
||||
return this.preloads.get(uri) || Promise.resolve(new Error(`Preload not ready: ${uri}`));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a preload.
|
||||
* @param uri URI to load from
|
||||
* @param originalUri URI to show in an error message if the preload is invalid.
|
||||
*/
|
||||
public load(uri: string, originalUri: string) {
|
||||
const promise = Promise.all([
|
||||
runPreload(uri, originalUri),
|
||||
this.waitForAllCurrent(),
|
||||
]).then(([module]) => module.activate());
|
||||
|
||||
this.preloads.set(uri, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that waits for all currently-registered preloads to
|
||||
* activate before resolving.
|
||||
*/
|
||||
private waitForAllCurrent() {
|
||||
return Promise.all([...this.preloads.values()].map(p => p.catch(err => err)));
|
||||
}
|
||||
};
|
||||
|
||||
const outputs = new class {
|
||||
private outputs = new Map<string, { cancelled: boolean; queue: Promise<unknown> }>();
|
||||
/**
|
||||
* Pushes the action onto the list of actions for the given output ID,
|
||||
* ensuring that it's run in-order.
|
||||
*/
|
||||
public enqueue(outputId: string, action: (record: { cancelled: boolean }) => unknown) {
|
||||
const record = this.outputs.get(outputId);
|
||||
if (!record) {
|
||||
this.outputs.set(outputId, { cancelled: false, queue: new Promise(r => r(action({ cancelled: false }))) });
|
||||
} else {
|
||||
record.queue = record.queue.then(r => !record.cancelled && action(record));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancells the rendering of all outputs.
|
||||
*/
|
||||
public cancelAll() {
|
||||
for (const record of this.outputs.values()) {
|
||||
record.cancelled = true;
|
||||
}
|
||||
this.outputs.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels any ongoing rendering out an output.
|
||||
*/
|
||||
public cancelOutput(outputId: string) {
|
||||
const output = this.outputs.get(outputId);
|
||||
if (output) {
|
||||
output.cancelled = true;
|
||||
this.outputs.delete(outputId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const renderers = new class {
|
||||
private readonly _renderers = new Map</* id */ string, Renderer>();
|
||||
|
||||
constructor() {
|
||||
for (const renderer of rendererData) {
|
||||
let loadPromise: Promise<MarkupRenderer> | undefined;
|
||||
|
||||
const entry = {
|
||||
load: () => {
|
||||
if (!loadPromise) {
|
||||
loadPromise = __import(renderer.entrypoint).then(module => {
|
||||
return module.activate({ dependencies: renderer.dependencies });
|
||||
});
|
||||
}
|
||||
return loadPromise;
|
||||
},
|
||||
renderer: undefined,
|
||||
};
|
||||
|
||||
for (const mime of renderer.mimeTypes || []) {
|
||||
if (!this.mimeTypesToRenderers.has(mime)) {
|
||||
this.mimeTypesToRenderers.set(mime, entry);
|
||||
this._renderers.set(renderer.id, new Renderer(renderer, async (extensionId) => {
|
||||
const ext = this._renderers.get(extensionId);
|
||||
if (!ext) {
|
||||
throw new Error(`Could not find extending renderer: ${extensionId}`);
|
||||
}
|
||||
}
|
||||
|
||||
await ext.load();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
async renderMarkdown(element: HTMLElement, content: string): Promise<void> {
|
||||
const entry = this.mimeTypesToRenderers.get('text/markdown');
|
||||
if (!entry) {
|
||||
public getRenderer(id: string): RendererApi | undefined {
|
||||
return this._renderers.get(id)?.api;
|
||||
}
|
||||
|
||||
public async load(id: string) {
|
||||
const renderer = this._renderers.get(id);
|
||||
if (!renderer) {
|
||||
throw new Error('Could not find renderer');
|
||||
}
|
||||
const renderer = await entry.load();
|
||||
renderer.renderMarkup({ element, content });
|
||||
|
||||
return renderer.load();
|
||||
}
|
||||
|
||||
|
||||
public clearAll() {
|
||||
outputs.cancelAll();
|
||||
for (const renderer of this._renderers.values()) {
|
||||
renderer.api?.destroyCell?.();
|
||||
}
|
||||
}
|
||||
|
||||
public clearOutput(rendererId: string, outputId: string) {
|
||||
outputs.cancelOutput(outputId);
|
||||
this._renderers.get(rendererId)?.api?.destroyCell?.(outputId);
|
||||
}
|
||||
|
||||
public async renderCustom(rendererId: string, outputId: string, info: ICreateCellInfo) {
|
||||
const api = await this.load(rendererId);
|
||||
if (!api) {
|
||||
throw new Error(`renderer ${rendererId} did not return an API`);
|
||||
}
|
||||
|
||||
api.renderCell(outputId, info);
|
||||
}
|
||||
|
||||
public async renderMarkdown(id: string, element: HTMLElement, content: string): Promise<void> {
|
||||
const markdownRenderers = Array.from(this._renderers.values())
|
||||
.filter(renderer => renderer.data.mimeTypes.includes('text/markdown') && !renderer.data.extends);
|
||||
|
||||
if (!markdownRenderers.length) {
|
||||
throw new Error('Could not find renderer');
|
||||
}
|
||||
|
||||
await Promise.all(markdownRenderers.map(x => x.load()));
|
||||
|
||||
markdownRenderers[0].api?.renderCell(id, {
|
||||
element,
|
||||
value: content,
|
||||
mime: 'text/markdown',
|
||||
metadata: undefined,
|
||||
outputId: undefined,
|
||||
});
|
||||
}
|
||||
}();
|
||||
|
||||
|
||||
vscode.postMessage({
|
||||
__vscode_notebook_message: true,
|
||||
type: 'initialized'
|
||||
|
@ -978,7 +1107,7 @@ async function webviewPreloads(style: PreloadStyles, rendererData: readonly Webv
|
|||
previewNode.innerText = '';
|
||||
} else {
|
||||
previewContainerNode.classList.remove('emptyMarkdownCell');
|
||||
await markupRenderers.renderMarkdown(previewNode, content);
|
||||
await renderers.renderMarkdown(cellId, previewNode, content);
|
||||
|
||||
if (!hasPostedRenderedMathTelemetry) {
|
||||
const hasRenderedMath = previewNode.querySelector('.katex');
|
||||
|
@ -1077,13 +1206,14 @@ async function webviewPreloads(style: PreloadStyles, rendererData: readonly Webv
|
|||
}();
|
||||
}
|
||||
|
||||
export interface WebviewPreloadRenderer {
|
||||
export interface RendererMetadata {
|
||||
readonly id: string;
|
||||
readonly entrypoint: string;
|
||||
readonly mimeTypes: readonly string[];
|
||||
readonly dependencies: ReadonlyArray<{ entrypoint: string }>;
|
||||
readonly extends: string | undefined;
|
||||
}
|
||||
|
||||
export function preloadsScriptStr(styleValues: PreloadStyles, renderers: readonly WebviewPreloadRenderer[]) {
|
||||
export function preloadsScriptStr(styleValues: PreloadStyles, renderers: readonly RendererMetadata[]) {
|
||||
// TS will try compiling `import()` in webviePreloads, so use an helper function instead
|
||||
// of using `import(...)` directly
|
||||
return `
|
||||
|
|
|
@ -147,7 +147,7 @@ export abstract class BaseCellViewModel extends Disposable {
|
|||
}));
|
||||
|
||||
this._register(this._viewContext.notebookOptions.onDidChangeOptions(e => {
|
||||
if (e.cellStatusBarVisibility) {
|
||||
if (e.cellStatusBarVisibility || e.insertToolbarPosition) {
|
||||
this.layoutChange({});
|
||||
}
|
||||
}));
|
||||
|
|
|
@ -179,8 +179,8 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod
|
|||
+ editorHeight
|
||||
+ statusbarHeight;
|
||||
const outputShowMoreContainerOffset = totalHeight
|
||||
- notebookLayoutConfiguration.bottomCellToolbarGap
|
||||
- notebookLayoutConfiguration.bottomCellToolbarHeight / 2
|
||||
- notebookLayoutConfiguration.bottomToolbarGap
|
||||
- notebookLayoutConfiguration.bottomToolbarHeight / 2
|
||||
- outputShowMoreContainerHeight;
|
||||
const bottomToolbarOffset = this.viewContext.notebookOptions.computeBottomToolbarOffset(totalHeight);
|
||||
const editorWidth = state.outerWidth !== undefined
|
||||
|
@ -209,11 +209,11 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod
|
|||
notebookLayoutConfiguration.cellTopMargin
|
||||
+ notebookLayoutConfiguration.collapsedIndicatorHeight
|
||||
+ notebookLayoutConfiguration.cellBottomMargin //CELL_BOTTOM_MARGIN
|
||||
+ notebookLayoutConfiguration.bottomCellToolbarGap //BOTTOM_CELL_TOOLBAR_GAP
|
||||
+ notebookLayoutConfiguration.bottomToolbarGap //BOTTOM_CELL_TOOLBAR_GAP
|
||||
+ outputTotalHeight + outputShowMoreContainerHeight;
|
||||
const outputShowMoreContainerOffset = totalHeight
|
||||
- notebookLayoutConfiguration.bottomCellToolbarGap
|
||||
- notebookLayoutConfiguration.bottomCellToolbarHeight / 2
|
||||
- notebookLayoutConfiguration.bottomToolbarGap
|
||||
- notebookLayoutConfiguration.bottomToolbarHeight / 2
|
||||
- outputShowMoreContainerHeight;
|
||||
const bottomToolbarOffset = this.viewContext.notebookOptions.computeBottomToolbarOffset(totalHeight);
|
||||
const editorWidth = state.outerWidth !== undefined
|
||||
|
@ -314,7 +314,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod
|
|||
+ this.getEditorStatusbarHeight()
|
||||
+ outputsTotalHeight
|
||||
+ outputShowMoreContainerHeight
|
||||
+ layoutConfiguration.bottomCellToolbarGap //BOTTOM_CELL_TOOLBAR_GAP
|
||||
+ layoutConfiguration.bottomToolbarGap //BOTTOM_CELL_TOOLBAR_GAP
|
||||
+ layoutConfiguration.cellBottomMargin; // CELL_BOTTOM_MARGIN;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie
|
|||
|
||||
set renderedMarkdownHeight(newHeight: number) {
|
||||
if (this.getEditState() === CellEditState.Preview) {
|
||||
const newTotalHeight = newHeight + this.viewContext.notebookOptions.getLayoutConfiguration().bottomCellToolbarGap; // BOTTOM_CELL_TOOLBAR_GAP;
|
||||
const newTotalHeight = newHeight + this.viewContext.notebookOptions.getLayoutConfiguration().bottomToolbarGap; // BOTTOM_CELL_TOOLBAR_GAP;
|
||||
this.totalHeight = newTotalHeight;
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie
|
|||
this.totalHeight = this._editorHeight
|
||||
+ layoutConfiguration.markdownCellTopMargin // MARKDOWN_CELL_TOP_MARGIN
|
||||
+ layoutConfiguration.markdownCellBottomMargin // MARKDOWN_CELL_BOTTOM_MARGIN
|
||||
+ layoutConfiguration.bottomCellToolbarGap // BOTTOM_CELL_TOOLBAR_GAP
|
||||
+ layoutConfiguration.bottomToolbarGap // BOTTOM_CELL_TOOLBAR_GAP
|
||||
+ this.viewContext.notebookOptions.computeStatusBarHeight();
|
||||
}
|
||||
|
||||
|
@ -120,7 +120,7 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie
|
|||
editorWidth: initialNotebookLayoutInfo?.width
|
||||
? this.viewContext.notebookOptions.computeMarkdownCellEditorWidth(initialNotebookLayoutInfo.width)
|
||||
: 0,
|
||||
bottomToolbarOffset: this.viewContext.notebookOptions.getLayoutConfiguration().bottomCellToolbarGap, // BOTTOM_CELL_TOOLBAR_GAP,
|
||||
bottomToolbarOffset: this.viewContext.notebookOptions.getLayoutConfiguration().bottomToolbarGap, // BOTTOM_CELL_TOOLBAR_GAP,
|
||||
totalHeight: 0
|
||||
};
|
||||
|
||||
|
|
|
@ -53,6 +53,8 @@ export const ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER = [
|
|||
export const BUILTIN_RENDERER_ID = '_builtin';
|
||||
export const RENDERER_NOT_AVAILABLE = '_notAvailable';
|
||||
|
||||
export type NotebookRendererEntrypoint = string | { extends: string; path: string };
|
||||
|
||||
export enum NotebookRunState {
|
||||
Running = 1,
|
||||
Idle = 2
|
||||
|
@ -132,6 +134,7 @@ export const enum NotebookRendererMatch {
|
|||
export interface INotebookRendererInfo {
|
||||
id: string;
|
||||
displayName: string;
|
||||
extends?: string;
|
||||
entrypoint: URI;
|
||||
preloads: ReadonlyArray<URI>;
|
||||
extensionLocation: URI;
|
||||
|
@ -894,6 +897,9 @@ export const ShowCellStatusBarKey = 'notebook.showCellStatusBar';
|
|||
export const NotebookTextDiffEditorPreview = 'notebook.diff.enablePreview';
|
||||
export const ExperimentalUseMarkdownRenderer = 'notebook.experimental.useMarkdownRenderer';
|
||||
export const ExperimentalCompactView = 'notebook.experimental.compactView';
|
||||
export const ExperimentalFocusIndicator = 'notebook.experimental.cellFocusIndicator';
|
||||
export const ExperimentalInsertToolbarPosition = 'notebook.experimental.insertToolbarPosition';
|
||||
export const ExperimentalGlobalToolbar = 'notebook.experimental.globalToolbar';
|
||||
export const ExperimentalUndoRedoPerCell = 'notebook.experimental.undoRedoPerCell';
|
||||
|
||||
export const enum CellStatusbarAlignment {
|
||||
|
|
|
@ -28,8 +28,9 @@ import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
|||
import { canceled } from 'vs/base/common/errors';
|
||||
import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IFileWorkingCopyManager } from 'vs/workbench/services/workingCopy/common/fileWorkingCopyManager';
|
||||
import { filter } from 'vs/base/common/objects';
|
||||
import { IFileWorkingCopyManager2 } from 'vs/workbench/services/workingCopy/common/fileWorkingCopyManager2';
|
||||
import { IResolvedUntitledFileWorkingCopy, IUntitledFileWorkingCopyModel, IUntitledFileWorkingCopyModelContentChangedEvent } from 'vs/workbench/services/workingCopy/common/untitledFileWorkingCopy';
|
||||
|
||||
//#region --- complex content provider
|
||||
|
||||
|
@ -425,13 +426,13 @@ export class SimpleNotebookEditorModel extends EditorModel implements INotebookE
|
|||
readonly onDidChangeOrphaned: Event<void> = this._onDidChangeOrphaned.event;
|
||||
readonly onDidChangeReadonly: Event<void> = this._onDidChangeReadonly.event;
|
||||
|
||||
private _workingCopy?: IResolvedFileWorkingCopy<NotebookFileWorkingCopyModel>;
|
||||
private _workingCopy?: IResolvedUntitledFileWorkingCopy<NotebookFileWorkingCopyModel> | IResolvedFileWorkingCopy<NotebookFileWorkingCopyModel>;
|
||||
private readonly _workingCopyListeners = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
readonly resource: URI,
|
||||
readonly viewType: string,
|
||||
private readonly _workingCopyManager: IFileWorkingCopyManager<NotebookFileWorkingCopyModel>,
|
||||
private readonly _workingCopyManager: IFileWorkingCopyManager2<NotebookFileWorkingCopyModel, NotebookFileWorkingCopyModel>,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IFileService private readonly _fileService: IFileService
|
||||
) {
|
||||
|
@ -461,11 +462,17 @@ export class SimpleNotebookEditorModel extends EditorModel implements INotebookE
|
|||
}
|
||||
|
||||
isOrphaned(): boolean {
|
||||
return this._workingCopy?.hasState(FileWorkingCopyState.ORPHAN) ?? false;
|
||||
return !!this._workingCopy && SimpleNotebookEditorModel._isFileWorkingCopy(this._workingCopy) && this._workingCopy.hasState(FileWorkingCopyState.ORPHAN);
|
||||
}
|
||||
|
||||
isReadonly(): boolean {
|
||||
return this._workingCopy?.isReadonly() || this._fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly);
|
||||
if (this._fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly)) {
|
||||
return true;
|
||||
} else if (this._workingCopy && SimpleNotebookEditorModel._isFileWorkingCopy(this._workingCopy)) {
|
||||
return this._workingCopy?.isReadonly();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
revert(options?: IRevertOptions): Promise<void> {
|
||||
|
@ -479,14 +486,26 @@ export class SimpleNotebookEditorModel extends EditorModel implements INotebookE
|
|||
}
|
||||
|
||||
async load(options?: INotebookLoadOptions): Promise<IResolvedNotebookEditorModel> {
|
||||
const workingCopy = await this._workingCopyManager.resolve(this.resource, { reload: { async: !options?.forceReadFromFile } });
|
||||
|
||||
if (!this._workingCopy) {
|
||||
this._workingCopy = <IResolvedFileWorkingCopy<NotebookFileWorkingCopyModel>>workingCopy;
|
||||
this._workingCopy.onDidChangeDirty(() => this._onDidChangeDirty.fire(), this._workingCopyListeners);
|
||||
this._workingCopy.onDidSave(() => this._onDidSave.fire(), this._workingCopyListeners);
|
||||
this._workingCopy.onDidChangeOrphaned(() => this._onDidChangeOrphaned.fire(), this._workingCopyListeners);
|
||||
this._workingCopy.onDidChangeReadonly(() => this._onDidChangeReadonly.fire(), this._workingCopyListeners);
|
||||
if (this.resource.scheme === Schemas.untitled) {
|
||||
const workingCopy = await this._workingCopyManager.resolve({ untitledResource: this.resource });
|
||||
this._workingCopy = <IResolvedUntitledFileWorkingCopy<NotebookFileWorkingCopyModel>>workingCopy;
|
||||
} else {
|
||||
const workingCopy = await this._workingCopyManager.resolve(this.resource, { forceReadFromFile: options?.forceReadFromFile });
|
||||
this._workingCopyListeners.add(workingCopy.onDidSave(() => this._onDidSave.fire()));
|
||||
this._workingCopyListeners.add(workingCopy.onDidChangeOrphaned(() => this._onDidChangeOrphaned.fire()));
|
||||
this._workingCopyListeners.add(workingCopy.onDidChangeReadonly(() => this._onDidChangeReadonly.fire()));
|
||||
this._workingCopy = <IResolvedFileWorkingCopy<NotebookFileWorkingCopyModel>>workingCopy;
|
||||
}
|
||||
this._workingCopy.onDidChangeDirty(() => this._onDidChangeDirty.fire(), undefined, this._workingCopyListeners);
|
||||
|
||||
this._workingCopyListeners.add(this._workingCopy.onWillDispose(() => {
|
||||
this._workingCopyListeners.clear();
|
||||
this._workingCopy?.model.dispose();
|
||||
}));
|
||||
}
|
||||
|
||||
assertType(this.isResolved());
|
||||
return this;
|
||||
}
|
||||
|
@ -501,11 +520,15 @@ export class SimpleNotebookEditorModel extends EditorModel implements INotebookE
|
|||
// the newly created editor input will pick it up and claim ownership of it.
|
||||
return this._instantiationService.createInstance(NotebookEditorInput, newWorkingCopy.resource, this.viewType, {});
|
||||
}
|
||||
|
||||
private static _isFileWorkingCopy(candidate: IResolvedUntitledFileWorkingCopy<NotebookFileWorkingCopyModel> | IResolvedFileWorkingCopy<NotebookFileWorkingCopyModel>): candidate is IResolvedFileWorkingCopy<NotebookFileWorkingCopyModel> {
|
||||
return typeof (<IResolvedFileWorkingCopy<NotebookFileWorkingCopyModel>>candidate).hasState === 'function';
|
||||
}
|
||||
}
|
||||
|
||||
export class NotebookFileWorkingCopyModel implements IFileWorkingCopyModel {
|
||||
export class NotebookFileWorkingCopyModel implements IFileWorkingCopyModel, IUntitledFileWorkingCopyModel {
|
||||
|
||||
private readonly _onDidChangeContent = new Emitter<IFileWorkingCopyModelContentChangedEvent>();
|
||||
private readonly _onDidChangeContent = new Emitter<IFileWorkingCopyModelContentChangedEvent & IUntitledFileWorkingCopyModelContentChangedEvent>();
|
||||
private readonly _changeListener: IDisposable;
|
||||
|
||||
readonly onDidChangeContent = this._onDidChangeContent.event;
|
||||
|
@ -525,10 +548,10 @@ export class NotebookFileWorkingCopyModel implements IFileWorkingCopyModel {
|
|||
if (rawEvent.transient) {
|
||||
continue;
|
||||
}
|
||||
//todo@jrieken,@rebornix forward this information from notebook model
|
||||
this._onDidChangeContent.fire({
|
||||
isRedoing: false,
|
||||
isUndoing: false
|
||||
isRedoing: false, //todo@rebornix forward this information from notebook model
|
||||
isUndoing: false,
|
||||
isEmpty: false, //_notebookModel.cells.length === 0 // todo@jrieken non transient metadata?
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
@ -585,7 +608,9 @@ export class NotebookFileWorkingCopyModel implements IFileWorkingCopyModel {
|
|||
this._notebookModel.reset(data.cells, data.metadata, this._notebookSerializer.options);
|
||||
}
|
||||
|
||||
get versionId() { return this._notebookModel.alternativeVersionId; }
|
||||
get versionId() {
|
||||
return this._notebookModel.alternativeVersionId;
|
||||
}
|
||||
|
||||
pushStackElement(): void {
|
||||
this._notebookModel.pushStackElement('save', undefined, undefined);
|
||||
|
@ -606,7 +631,8 @@ export class NotebookFileWorkingCopyModelFactory implements IFileWorkingCopyMode
|
|||
throw new Error('CANNOT open file notebook with this provider');
|
||||
}
|
||||
|
||||
const data = await info.serializer.dataToNotebook(await streamToBuffer(stream));
|
||||
const bytes = await streamToBuffer(stream);
|
||||
const data = await info.serializer.dataToNotebook(bytes);
|
||||
|
||||
if (token.isCancellationRequested) {
|
||||
throw canceled();
|
||||
|
|
|
@ -11,16 +11,16 @@ import { combinedDisposable, DisposableStore, dispose, IDisposable, IReference,
|
|||
import { ComplexNotebookProviderInfo, INotebookService, SimpleNotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { FileWorkingCopyManager, IFileWorkingCopyManager } from 'vs/workbench/services/workingCopy/common/fileWorkingCopyManager';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
|
||||
import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
import { FileWorkingCopyManager2, IFileWorkingCopyManager2 } from 'vs/workbench/services/workingCopy/common/fileWorkingCopyManager2';
|
||||
|
||||
class NotebookModelReferenceCollection extends ReferenceCollection<Promise<IResolvedNotebookEditorModel>> {
|
||||
|
||||
private readonly _disposables = new DisposableStore();
|
||||
private readonly _workingCopyManagers = new Map<string, IFileWorkingCopyManager<NotebookFileWorkingCopyModel>>();
|
||||
private readonly _workingCopyManagers = new Map<string, IFileWorkingCopyManager2<NotebookFileWorkingCopyModel, NotebookFileWorkingCopyModel>>();
|
||||
private readonly _modelListener = new Map<IResolvedNotebookEditorModel, IDisposable>();
|
||||
|
||||
private readonly _onDidSaveNotebook = new Emitter<URI>();
|
||||
|
@ -70,10 +70,12 @@ class NotebookModelReferenceCollection extends ReferenceCollection<Promise<IReso
|
|||
const workingCopyTypeId = NotebookWorkingCopyTypeIdentifier.create(viewType);
|
||||
let workingCopyManager = this._workingCopyManagers.get(workingCopyTypeId);
|
||||
if (!workingCopyManager) {
|
||||
workingCopyManager = <IFileWorkingCopyManager<NotebookFileWorkingCopyModel>><any>this._instantiationService.createInstance(
|
||||
FileWorkingCopyManager,
|
||||
const factory = new NotebookFileWorkingCopyModelFactory(viewType, this._notebookService);
|
||||
workingCopyManager = <IFileWorkingCopyManager2<NotebookFileWorkingCopyModel, NotebookFileWorkingCopyModel>><any>this._instantiationService.createInstance(
|
||||
FileWorkingCopyManager2,
|
||||
workingCopyTypeId,
|
||||
new NotebookFileWorkingCopyModelFactory(viewType, this._notebookService)
|
||||
factory,
|
||||
factory,
|
||||
);
|
||||
this._workingCopyManagers.set(workingCopyTypeId, workingCopyManager);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { CellToolbarLocKey, CellToolbarVisibility, ExperimentalCompactView, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { CellToolbarLocKey, CellToolbarVisibility, ExperimentalCompactView, ExperimentalFocusIndicator, ExperimentalGlobalToolbar, ExperimentalInsertToolbarPosition, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
|
||||
const SCROLLABLE_ELEMENT_PADDING_TOP = 18;
|
||||
|
||||
let EDITOR_TOP_PADDING = 12;
|
||||
const editorTopPaddingChangeEmitter = new Emitter<void>();
|
||||
|
@ -33,8 +35,8 @@ export interface NotebookLayoutConfiguration {
|
|||
markdownCellTopMargin: number;
|
||||
markdownCellBottomMargin: number;
|
||||
markdownPreviewPadding: number;
|
||||
bottomCellToolbarGap: number;
|
||||
bottomCellToolbarHeight: number;
|
||||
bottomToolbarGap: number;
|
||||
bottomToolbarHeight: number;
|
||||
editorToolbarHeight: number;
|
||||
editorTopPadding: number;
|
||||
editorBottomPadding: number;
|
||||
|
@ -45,6 +47,9 @@ export interface NotebookLayoutConfiguration {
|
|||
cellToolbarLocation: string | { [key: string]: string };
|
||||
cellToolbarInteraction: string;
|
||||
compactView: boolean;
|
||||
focusIndicator: 'border' | 'gutter';
|
||||
insertToolbarPosition: 'betweenCells' | 'notebookToolbar' | 'both' | 'hidden';
|
||||
globalToolbar: boolean;
|
||||
}
|
||||
|
||||
interface NotebookOptionsChangeEvent {
|
||||
|
@ -53,6 +58,9 @@ interface NotebookOptionsChangeEvent {
|
|||
cellToolbarInteraction?: boolean;
|
||||
editorTopPadding?: boolean;
|
||||
compactView?: boolean;
|
||||
focusIndicator?: boolean;
|
||||
insertToolbarPosition?: boolean;
|
||||
globalToolbar?: boolean;
|
||||
}
|
||||
|
||||
const defaultConfigConstants = {
|
||||
|
@ -61,7 +69,6 @@ const defaultConfigConstants = {
|
|||
markdownCellTopMargin: 8,
|
||||
markdownCellBottomMargin: 8,
|
||||
markdownCellLeftMargin: 32,
|
||||
bottomCellToolbarGap: 18,
|
||||
};
|
||||
|
||||
const compactConfigConstants = {
|
||||
|
@ -70,7 +77,6 @@ const compactConfigConstants = {
|
|||
markdownCellTopMargin: 6,
|
||||
markdownCellBottomMargin: 6,
|
||||
markdownCellLeftMargin: 32,
|
||||
bottomCellToolbarGap: 12,
|
||||
};
|
||||
|
||||
export class NotebookOptions {
|
||||
|
@ -81,9 +87,13 @@ export class NotebookOptions {
|
|||
|
||||
constructor(readonly configurationService: IConfigurationService) {
|
||||
const showCellStatusBar = this.configurationService.getValue<boolean>(ShowCellStatusBarKey);
|
||||
const globalToolbar = this.configurationService.getValue<boolean | undefined>(ExperimentalGlobalToolbar) ?? false;
|
||||
const cellToolbarLocation = this.configurationService.getValue<string | { [key: string]: string }>(CellToolbarLocKey);
|
||||
const cellToolbarInteraction = this.configurationService.getValue<string>(CellToolbarVisibility);
|
||||
const compactView = this.configurationService.getValue<boolean>(ExperimentalCompactView);
|
||||
const focusIndicator = this.configurationService.getValue<'border' | 'gutter'>(ExperimentalFocusIndicator) ?? 'border';
|
||||
const insertToolbarPosition = this.configurationService.getValue<'betweenCells' | 'notebookToolbar' | 'both' | 'hidden'>(ExperimentalInsertToolbarPosition) ?? 'both';
|
||||
const { bottomToolbarGap, bottomToolbarHeight } = this._computeBottomToolbarDimensions(compactView, insertToolbarPosition);
|
||||
|
||||
this._disposables = [];
|
||||
this._layoutConfiguration = {
|
||||
|
@ -94,16 +104,20 @@ export class NotebookOptions {
|
|||
cellStatusBarHeight: 22,
|
||||
cellOutputPadding: 14,
|
||||
markdownPreviewPadding: 8,
|
||||
bottomCellToolbarHeight: 22,
|
||||
bottomToolbarHeight: bottomToolbarHeight,
|
||||
bottomToolbarGap: bottomToolbarGap,
|
||||
editorToolbarHeight: 0,
|
||||
editorTopPadding: EDITOR_TOP_PADDING,
|
||||
editorBottomPadding: 4,
|
||||
editorBottomPaddingWithoutStatusBar: 12,
|
||||
collapsedIndicatorHeight: 24,
|
||||
showCellStatusBar,
|
||||
globalToolbar,
|
||||
cellToolbarLocation,
|
||||
cellToolbarInteraction,
|
||||
compactView
|
||||
compactView,
|
||||
focusIndicator,
|
||||
insertToolbarPosition
|
||||
};
|
||||
|
||||
this._disposables.push(this.configurationService.onDidChangeConfiguration(e => {
|
||||
|
@ -111,8 +125,11 @@ export class NotebookOptions {
|
|||
let cellToolbarLocation = e.affectsConfiguration(CellToolbarLocKey);
|
||||
let cellToolbarInteraction = e.affectsConfiguration(CellToolbarVisibility);
|
||||
let compactView = e.affectsConfiguration(ExperimentalCompactView);
|
||||
let focusIndicator = e.affectsConfiguration(ExperimentalFocusIndicator);
|
||||
let insertToolbarPosition = e.affectsConfiguration(ExperimentalInsertToolbarPosition);
|
||||
let globalToolbar = e.affectsConfiguration(ExperimentalGlobalToolbar);
|
||||
|
||||
if (!cellStatusBarVisibility && !cellToolbarLocation && !cellToolbarInteraction && !compactView) {
|
||||
if (!cellStatusBarVisibility && !cellToolbarLocation && !cellToolbarInteraction && !compactView && !focusIndicator && !insertToolbarPosition && !globalToolbar) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -130,6 +147,10 @@ export class NotebookOptions {
|
|||
configuration.cellToolbarInteraction = this.configurationService.getValue<string>(CellToolbarVisibility);
|
||||
}
|
||||
|
||||
if (focusIndicator) {
|
||||
configuration.focusIndicator = this.configurationService.getValue<'border' | 'gutter'>(ExperimentalFocusIndicator) ?? 'border';
|
||||
}
|
||||
|
||||
if (compactView) {
|
||||
const compactViewValue = this.configurationService.getValue<boolean>('notebook.experimental.compactView');
|
||||
configuration = Object.assign(configuration, {
|
||||
|
@ -138,6 +159,17 @@ export class NotebookOptions {
|
|||
configuration.compactView = compactViewValue;
|
||||
}
|
||||
|
||||
if (insertToolbarPosition) {
|
||||
configuration.insertToolbarPosition = this.configurationService.getValue<'betweenCells' | 'notebookToolbar' | 'both' | 'hidden'>(ExperimentalInsertToolbarPosition) ?? 'both';
|
||||
const { bottomToolbarGap, bottomToolbarHeight } = this._computeBottomToolbarDimensions(configuration.compactView, configuration.insertToolbarPosition);
|
||||
configuration.bottomToolbarHeight = bottomToolbarHeight;
|
||||
configuration.bottomToolbarGap = bottomToolbarGap;
|
||||
}
|
||||
|
||||
if (globalToolbar) {
|
||||
configuration.globalToolbar = this.configurationService.getValue<boolean | undefined>(ExperimentalGlobalToolbar) ?? false;
|
||||
}
|
||||
|
||||
this._layoutConfiguration = configuration;
|
||||
|
||||
// trigger event
|
||||
|
@ -145,7 +177,10 @@ export class NotebookOptions {
|
|||
cellStatusBarVisibility: cellStatusBarVisibility,
|
||||
cellToolbarLocation: cellToolbarLocation,
|
||||
cellToolbarInteraction: cellToolbarInteraction,
|
||||
compactView: compactView
|
||||
compactView: compactView,
|
||||
focusIndicator: focusIndicator,
|
||||
insertToolbarPosition: insertToolbarPosition,
|
||||
globalToolbar: globalToolbar
|
||||
});
|
||||
}));
|
||||
|
||||
|
@ -157,6 +192,23 @@ export class NotebookOptions {
|
|||
}));
|
||||
}
|
||||
|
||||
private _computeBottomToolbarDimensions(compactView: boolean, insertToolbarPosition: 'betweenCells' | 'notebookToolbar' | 'both' | 'hidden'): { bottomToolbarGap: number, bottomToolbarHeight: number } {
|
||||
if (insertToolbarPosition === 'betweenCells' || insertToolbarPosition === 'both') {
|
||||
return compactView ? {
|
||||
bottomToolbarGap: 12,
|
||||
bottomToolbarHeight: 22
|
||||
} : {
|
||||
bottomToolbarGap: 18,
|
||||
bottomToolbarHeight: 22
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
bottomToolbarGap: 0,
|
||||
bottomToolbarHeight: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
getLayoutConfiguration(): NotebookLayoutConfiguration {
|
||||
return this._layoutConfiguration;
|
||||
}
|
||||
|
@ -164,14 +216,14 @@ export class NotebookOptions {
|
|||
computeCollapsedMarkdownCellHeight(): number {
|
||||
return this._layoutConfiguration.markdownCellTopMargin
|
||||
+ this._layoutConfiguration.collapsedIndicatorHeight
|
||||
+ this._layoutConfiguration.bottomCellToolbarGap
|
||||
+ this._layoutConfiguration.bottomToolbarGap
|
||||
+ this._layoutConfiguration.markdownCellBottomMargin;
|
||||
}
|
||||
|
||||
computeBottomToolbarOffset(totalHeight: number) {
|
||||
return totalHeight
|
||||
- this._layoutConfiguration.bottomCellToolbarGap
|
||||
- this._layoutConfiguration.bottomCellToolbarHeight / 2;
|
||||
- this._layoutConfiguration.bottomToolbarGap
|
||||
- this._layoutConfiguration.bottomToolbarHeight / 2;
|
||||
}
|
||||
|
||||
computeCodeCellEditorWidth(outerWidth: number): number {
|
||||
|
@ -265,11 +317,19 @@ export class NotebookOptions {
|
|||
|
||||
computeIndicatorPosition(totalHeight: number) {
|
||||
return {
|
||||
bottomIndicatorTop: totalHeight - this._layoutConfiguration.bottomCellToolbarGap - this._layoutConfiguration.cellBottomMargin,
|
||||
verticalIndicatorHeight: totalHeight - this._layoutConfiguration.bottomCellToolbarGap
|
||||
bottomIndicatorTop: totalHeight - this._layoutConfiguration.bottomToolbarGap - this._layoutConfiguration.cellBottomMargin,
|
||||
verticalIndicatorHeight: totalHeight - this._layoutConfiguration.bottomToolbarGap
|
||||
};
|
||||
}
|
||||
|
||||
computeTopInserToolbarHeight(): number {
|
||||
if (this._layoutConfiguration.insertToolbarPosition === 'betweenCells' || this._layoutConfiguration.insertToolbarPosition === 'both') {
|
||||
return SCROLLABLE_ELEMENT_PADDING_TOP;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._disposables.forEach(d => d.dispose());
|
||||
this._disposables = [];
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Iterable } from 'vs/base/common/iterator';
|
|||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { INotebookRendererInfo, NotebookRendererMatch } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { INotebookRendererInfo, NotebookRendererEntrypoint, NotebookRendererMatch } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
|
||||
class DependencyList {
|
||||
private readonly value: ReadonlySet<string>;
|
||||
|
@ -34,6 +34,7 @@ class DependencyList {
|
|||
export class NotebookOutputRendererInfo implements INotebookRendererInfo {
|
||||
|
||||
readonly id: string;
|
||||
readonly extends?: string;
|
||||
readonly entrypoint: URI;
|
||||
readonly displayName: string;
|
||||
readonly extensionLocation: URI;
|
||||
|
@ -49,7 +50,7 @@ export class NotebookOutputRendererInfo implements INotebookRendererInfo {
|
|||
constructor(descriptor: {
|
||||
readonly id: string;
|
||||
readonly displayName: string;
|
||||
readonly entrypoint: string;
|
||||
readonly entrypoint: NotebookRendererEntrypoint;
|
||||
readonly mimeTypes: readonly string[];
|
||||
readonly extension: IExtensionDescription;
|
||||
readonly dependencies: readonly string[] | undefined;
|
||||
|
@ -58,7 +59,14 @@ export class NotebookOutputRendererInfo implements INotebookRendererInfo {
|
|||
this.id = descriptor.id;
|
||||
this.extensionId = descriptor.extension.identifier;
|
||||
this.extensionLocation = descriptor.extension.extensionLocation;
|
||||
this.entrypoint = joinPath(this.extensionLocation, descriptor.entrypoint);
|
||||
|
||||
if (typeof descriptor.entrypoint === 'string') {
|
||||
this.entrypoint = joinPath(this.extensionLocation, descriptor.entrypoint);
|
||||
} else {
|
||||
this.extends = descriptor.entrypoint.extends;
|
||||
this.entrypoint = joinPath(this.extensionLocation, descriptor.entrypoint.path);
|
||||
}
|
||||
|
||||
this.displayName = descriptor.displayName;
|
||||
this.mimeTypes = descriptor.mimeTypes;
|
||||
this.mimeTypeGlobs = this.mimeTypes.map(pattern => glob.parse(pattern));
|
||||
|
@ -103,6 +111,10 @@ export class NotebookOutputRendererInfo implements INotebookRendererInfo {
|
|||
}
|
||||
|
||||
private matchesMimeTypeOnly(mimeType: string) {
|
||||
if (this.extends !== undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.mimeTypeGlobs.some(pattern => pattern(mimeType)) || this.mimeTypes.some(pattern => pattern === mimeType);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,15 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions';
|
||||
import { createNotebookCellList, setupInstantiationService, withTestNotebook } from 'vs/workbench/contrib/notebook/test/testNotebookEditor';
|
||||
|
||||
suite('NotebookCellList', () => {
|
||||
const instantiationService = setupInstantiationService();
|
||||
const notebookDefaultOptions = new NotebookOptions(instantiationService.get(IConfigurationService));
|
||||
const topInsertToolbarHeight = notebookDefaultOptions.computeTopInserToolbarHeight();
|
||||
|
||||
test('revealElementsInView: reveal fully visible cell should not scroll', async function () {
|
||||
await withTestNotebook(
|
||||
|
@ -32,7 +35,7 @@ suite('NotebookCellList', () => {
|
|||
cellList.attachViewModel(viewModel);
|
||||
|
||||
// render height 210, it can render 3 full cells and 1 partial cell
|
||||
cellList.layout(210 + SCROLLABLE_ELEMENT_PADDING_TOP, 100);
|
||||
cellList.layout(210 + topInsertToolbarHeight, 100);
|
||||
// scroll a bit, scrollTop to bottom: 5, 215
|
||||
cellList.scrollTop = 5;
|
||||
|
||||
|
@ -77,7 +80,7 @@ suite('NotebookCellList', () => {
|
|||
cellList.attachViewModel(viewModel);
|
||||
|
||||
// render height 210, it can render 3 full cells and 1 partial cell
|
||||
cellList.layout(210 + SCROLLABLE_ELEMENT_PADDING_TOP, 100);
|
||||
cellList.layout(210 + topInsertToolbarHeight, 100);
|
||||
|
||||
// init scrollTop and scrollBottom
|
||||
assert.deepStrictEqual(cellList.scrollTop, 0);
|
||||
|
@ -116,12 +119,12 @@ suite('NotebookCellList', () => {
|
|||
});
|
||||
|
||||
const cellList = createNotebookCellList(instantiationService);
|
||||
// without additionalscrollheight, the last 20 px will always be hidden due to `SCROLLABLE_ELEMENT_PADDING_TOP`
|
||||
// without additionalscrollheight, the last 20 px will always be hidden due to `topInsertToolbarHeight`
|
||||
cellList.updateOptions({ additionalScrollHeight: 100 });
|
||||
cellList.attachViewModel(viewModel);
|
||||
|
||||
// render height 210, it can render 3 full cells and 1 partial cell
|
||||
cellList.layout(210 + SCROLLABLE_ELEMENT_PADDING_TOP, 100);
|
||||
cellList.layout(210 + topInsertToolbarHeight, 100);
|
||||
|
||||
// init scrollTop and scrollBottom
|
||||
assert.deepStrictEqual(cellList.scrollTop, 0);
|
||||
|
@ -154,7 +157,7 @@ suite('NotebookCellList', () => {
|
|||
cellList.attachViewModel(viewModel);
|
||||
|
||||
// render height 210, it can render 3 full cells and 1 partial cell
|
||||
cellList.layout(210 + SCROLLABLE_ELEMENT_PADDING_TOP, 100);
|
||||
cellList.layout(210 + topInsertToolbarHeight, 100);
|
||||
|
||||
// init scrollTop and scrollBottom
|
||||
assert.deepStrictEqual(cellList.scrollTop, 0);
|
||||
|
@ -196,7 +199,7 @@ suite('NotebookCellList', () => {
|
|||
cellList.attachViewModel(viewModel);
|
||||
|
||||
// render height 210, it can render 3 full cells and 1 partial cell
|
||||
cellList.layout(210 + SCROLLABLE_ELEMENT_PADDING_TOP, 100);
|
||||
cellList.layout(210 + topInsertToolbarHeight, 100);
|
||||
|
||||
// init scrollTop and scrollBottom
|
||||
assert.deepStrictEqual(cellList.scrollTop, 0);
|
||||
|
@ -249,7 +252,7 @@ suite('NotebookCellList', () => {
|
|||
cellList.attachViewModel(viewModel);
|
||||
|
||||
// render height 210, it can render 3 full cells and 1 partial cell
|
||||
cellList.layout(210 + SCROLLABLE_ELEMENT_PADDING_TOP, 100);
|
||||
cellList.layout(210 + topInsertToolbarHeight, 100);
|
||||
|
||||
// init scrollTop and scrollBottom
|
||||
assert.deepStrictEqual(cellList.scrollTop, 0);
|
||||
|
@ -283,7 +286,7 @@ suite('NotebookCellList', () => {
|
|||
cellList.attachViewModel(viewModel);
|
||||
|
||||
// render height 210, it can render 3 full cells and 1 partial cell
|
||||
cellList.layout(210 + SCROLLABLE_ELEMENT_PADDING_TOP, 100);
|
||||
cellList.layout(210 + topInsertToolbarHeight, 100);
|
||||
|
||||
// init scrollTop and scrollBottom
|
||||
assert.deepStrictEqual(cellList.scrollTop, 0);
|
||||
|
|
Loading…
Reference in a new issue