diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index d4f775ccc4503e5f2f3dc76f780922c63b279f2c..0b792160eab2f193a503528bcd4f85a6f12a5c81 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1294,11 +1294,17 @@ declare module 'vscode' { * The range will always be revealed in the center of the viewport. */ InCenter = 1, + /** * If the range is outside the viewport, it will be revealed in the center of the viewport. * Otherwise, it will be revealed with as little scrolling as possible. */ InCenterIfOutsideViewport = 2, + + /** + * The range will always be revealed at the top of the viewport. + */ + AtTop = 3 } export interface NotebookEditor { diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 4e361ea1fc46d130be3f3a23ea5cd70272b5f1f9..0b4b7f3f55d9df8e5e087e9e6bd0e2796d5f7810 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -646,14 +646,13 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo switch (revealType) { case NotebookEditorRevealType.Default: - notebookEditor.revealInView(cell); - break; + return notebookEditor.revealCellRangeInView(range); case NotebookEditorRevealType.InCenter: - notebookEditor.revealInCenter(cell); - break; + return notebookEditor.revealInCenter(cell); case NotebookEditorRevealType.InCenterIfOutsideViewport: - notebookEditor.revealInCenterIfOutsideViewport(cell); - break; + return notebookEditor.revealInCenterIfOutsideViewport(cell); + case NotebookEditorRevealType.AtTop: + return notebookEditor.revealInViewAtTop(cell); default: break; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index a5f6d3ed535610cf1d12066083f2a3595bb5af31..4f24459ce5006ce4b0f5efc5cddb428eba42af1e 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -754,6 +754,7 @@ export enum NotebookEditorRevealType { Default = 0, InCenter = 1, InCenterIfOutsideViewport = 2, + AtTop = 3 } export interface INotebookDocumentShowOptions { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index e153af8aa460251650d656079590a3fcfcb46c50..fbd6592c6616b1712c4dad159584800e915560e1 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2858,7 +2858,8 @@ export enum NotebookCellStatusBarAlignment { export enum NotebookEditorRevealType { Default = 0, InCenter = 1, - InCenterIfOutsideViewport = 2 + InCenterIfOutsideViewport = 2, + AtTop = 3 } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index d06ec99a6b2ce23c57fb69e8e4a167fb9668e2b8..b5ed5c132a184e6e5669c562ae71c6939fb1e117 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -500,11 +500,21 @@ export interface INotebookEditor extends IEditor, ICommonNotebookEditor { */ triggerScroll(event: IMouseWheelEvent): void; + /** + * The range will be revealed with as little scrolling as possible. + */ + revealCellRangeInView(range: ICellRange): void; + /** * Reveal cell into viewport. */ revealInView(cell: ICellViewModel): void; + /** + * Reveal cell into the top of viewport. + */ + revealInViewAtTop(cell: ICellViewModel): void; + /** * Reveal cell into viewport center. */ @@ -614,7 +624,9 @@ export interface INotebookCellList { focusElement(element: ICellViewModel): void; selectElement(element: ICellViewModel): void; getFocusedElements(): ICellViewModel[]; + revealElementsInView(range: ICellRange): void; revealElementInView(element: ICellViewModel): void; + revealElementInViewAtTop(element: ICellViewModel): void; revealElementInCenterIfOutsideViewport(element: ICellViewModel): void; revealElementInCenter(element: ICellViewModel): void; revealElementInCenterIfOutsideViewportAsync(element: ICellViewModel): Promise; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index f0f93c38438dab95a2945d193f781451c335f972..036f4cb0f254693c0a734c09ed90be81b70ff499 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1226,10 +1226,18 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor // this.viewModel!.selectionHandles = [cell.handle]; } + revealCellRangeInView(range: ICellRange) { + return this._list.revealElementsInView(range); + } + revealInView(cell: ICellViewModel) { this._list.revealElementInView(cell); } + revealInViewAtTop(cell: ICellViewModel) { + this._list.revealElementInViewAtTop(cell); + } + revealInCenterIfOutsideViewport(cell: ICellViewModel) { this._list.revealElementInCenterIfOutsideViewport(cell); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 8de660ff484776061dd10944a9cb65e1c26b91d8..8d0c6a5422a9d17ca00759d395e9956a1e32ae9f 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -547,6 +547,22 @@ export class NotebookCellList extends WorkbenchList implements ID return viewIndexInfo.index; } + private _getViewIndexUpperBound2(modelIndex: number) { + if (!this.hiddenRangesPrefixSum) { + return modelIndex; + } + + const viewIndexInfo = this.hiddenRangesPrefixSum.getIndexOf(modelIndex); + + if (viewIndexInfo.remainder !== 0) { + if (modelIndex >= this.hiddenRangesPrefixSum.getTotalValue()) { + return modelIndex - (this.hiddenRangesPrefixSum.getTotalValue() - this.hiddenRangesPrefixSum.getCount()); + } + } + + return viewIndexInfo.index; + } + focusElement(cell: ICellViewModel) { const index = this._getViewIndexUpperBound(cell); @@ -587,6 +603,46 @@ export class NotebookCellList extends WorkbenchList implements ID super.setFocus(indexes, browserEvent); } + revealElementsInView(range: ICellRange) { + const startIndex = this._getViewIndexUpperBound2(range.start); + + if (startIndex < 0) { + return; + } + + const endIndex = this._getViewIndexUpperBound2(range.end); + + const scrollTop = this.getViewScrollTop(); + const wrapperBottom = this.getViewScrollBottom(); + const elementTop = this.view.elementTop(startIndex); + if (elementTop >= scrollTop + && elementTop < wrapperBottom) { + // start element is visible + // check end + + const endElementTop = this.view.elementTop(endIndex); + const endElementHeight = this.view.elementHeight(endIndex); + + if (endElementTop >= wrapperBottom) { + return this._revealInternal(startIndex, false, CellRevealPosition.Top); + } + + if (endElementTop < wrapperBottom) { + // end element partially visible + if (endElementTop + endElementHeight - wrapperBottom < elementTop - scrollTop) { + // there is enough space to just scroll up a little bit to make the end element visible + return this.view.setScrollTop(scrollTop + endElementTop + endElementHeight - wrapperBottom); + } else { + // don't even try it + return this._revealInternal(startIndex, false, CellRevealPosition.Top); + } + } + } + + + this._revealInView(startIndex); + } + revealElementInView(cell: ICellViewModel) { const index = this._getViewIndexUpperBound(cell); @@ -595,6 +651,14 @@ export class NotebookCellList extends WorkbenchList implements ID } } + revealElementInViewAtTop(cell: ICellViewModel) { + const index = this._getViewIndexUpperBound(cell); + + if (index >= 0) { + this._revealInternal(index, false, CellRevealPosition.Top); + } + } + revealElementInCenterIfOutsideViewport(cell: ICellViewModel) { const index = this._getViewIndexUpperBound(cell); diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index ff58b2ef751f9b6bda4e9ee47945dba99153bc31..9ee463274b79e06e197b91b4978eb9d9ac30f199 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -275,7 +275,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel const mainCells = cells.map(cell => { const cellHandle = this._cellhandlePool++; - const cellUri = CellUri.generate(this.uri, cellHandle); + const cellUri = CellUri.generate(this.uri, this.viewType, cellHandle); return new NotebookCellTextModel(cellUri, cellHandle, cell.source, cell.language, cell.cellKind, cell.outputs || [], cell.metadata, this.transientOptions, this._modelService); }); @@ -425,7 +425,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel // prepare add const cells = cellDtos.map(cellDto => { const cellHandle = this._cellhandlePool++; - const cellUri = CellUri.generate(this.uri, cellHandle); + const cellUri = CellUri.generate(this.uri, this.viewType, cellHandle); const cell = new NotebookCellTextModel( cellUri, cellHandle, cellDto.source, cellDto.language, cellDto.cellKind, cellDto.outputs || [], cellDto.metadata, this.transientOptions, diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index a2f99da121d83e8100a32952cedbdd002ccdf488..cbc5b9938ee448794853c76c45de11bb8c2d7935 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -467,7 +467,7 @@ export function getCellUndoRedoComparisonKey(uri: URI) { return uri.toString(); } - return data.notebook.toString(); + return `vt=${data.viewType}&uri=data.notebook.toString()`; } @@ -477,10 +477,11 @@ export namespace CellUri { const _regex = /^ch(\d{7,})/; - export function generate(notebook: URI, handle: number): URI { + export function generate(notebook: URI, viewType: string, handle: number): URI { return notebook.with({ scheme, - fragment: `ch${handle.toString().padStart(7, '0')}${notebook.scheme !== Schemas.file ? notebook.scheme : ''}` + fragment: `ch${handle.toString().padStart(7, '0')}${notebook.scheme !== Schemas.file ? notebook.scheme : ''}`, + query: `vt=${viewType}` }); } @@ -492,7 +493,7 @@ export namespace CellUri { }); } - export function parse(cell: URI): { notebook: URI, handle: number } | undefined { + export function parse(cell: URI): { notebook: URI, handle: number, viewType: string } | undefined { if (cell.scheme !== scheme) { return undefined; } @@ -505,8 +506,10 @@ export namespace CellUri { handle, notebook: cell.with({ scheme: cell.fragment.substr(match[0].length) || Schemas.file, - fragment: null - }) + fragment: null, + query: null + }), + viewType: cell.query.substr('vt='.length) }; } } diff --git a/src/vs/workbench/contrib/notebook/test/notebookCommon.test.ts b/src/vs/workbench/contrib/notebook/test/notebookCommon.test.ts index 38a91a236ce7395462c8276fb68a74ea3d52db57..2104ef1586dee7983ae69dd7492b7c33bce50e9e 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookCommon.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookCommon.test.ts @@ -336,11 +336,12 @@ suite('CellUri', function () { const nb = URI.parse('foo:///bar/følder/file.nb'); const id = 17; - const data = CellUri.generate(nb, id); + const data = CellUri.generate(nb, 'test', id); const actual = CellUri.parse(data); assert.ok(Boolean(actual)); assert.equal(actual?.handle, id); assert.equal(actual?.notebook.toString(), nb.toString()); + assert.equal(actual?.viewType, 'test'); }); test('parse, generate (foo-scheme)', function () { @@ -348,10 +349,11 @@ suite('CellUri', function () { const nb = URI.parse('foo:///bar/følder/file.nb'); const id = 17; - const data = CellUri.generate(nb, id); + const data = CellUri.generate(nb, 'test', id); const actual = CellUri.parse(data); assert.ok(Boolean(actual)); assert.equal(actual?.handle, id); assert.equal(actual?.notebook.toString(), nb.toString()); + assert.equal(actual?.viewType, 'test'); }); }); diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index fa820f8fb9d7ff763cbea6a03d5d139384431d58..0d12ec2f4b6946bd56aa8877b01c2ed858a67bd3 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -47,7 +47,7 @@ export class TestCell extends NotebookCellTextModel { outputs: IProcessedOutput[], modelService: ITextModelService ) { - super(CellUri.generate(URI.parse('test:///fake/notebook'), handle), handle, source, language, cellKind, outputs, undefined, { transientMetadata: {}, transientOutputs: false }, modelService); + super(CellUri.generate(URI.parse('test:///fake/notebook'), viewType, handle), handle, source, language, cellKind, outputs, undefined, { transientMetadata: {}, transientOutputs: false }, modelService); } } diff --git a/src/vs/workbench/test/browser/api/extHostNotebook.test.ts b/src/vs/workbench/test/browser/api/extHostNotebook.test.ts index 9b3a4c2ec2a94845f9dd631fc48eb24b1868b130..e2d51f7cceb6c47b355806a215c5c4aea0073876 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebook.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebook.test.ts @@ -62,7 +62,7 @@ suite('NotebookCell#Document', function () { versionId: 0, cells: [{ handle: 0, - uri: CellUri.generate(notebookUri, 0), + uri: CellUri.generate(notebookUri, 'test', 0), source: ['### Heading'], eol: '\n', language: 'markdown', @@ -70,7 +70,7 @@ suite('NotebookCell#Document', function () { outputs: [], }, { handle: 1, - uri: CellUri.generate(notebookUri, 1), + uri: CellUri.generate(notebookUri, 'test', 1), source: ['console.log("aaa")', 'console.log("bbb")'], eol: '\n', language: 'javascript', @@ -167,7 +167,7 @@ suite('NotebookCell#Document', function () { kind: NotebookCellsChangeType.ModelChange, changes: [[0, 0, [{ handle: 2, - uri: CellUri.generate(notebookUri, 2), + uri: CellUri.generate(notebookUri, 'test', 2), source: ['Hello', 'World', 'Hello World!'], eol: '\n', language: 'test', @@ -175,7 +175,7 @@ suite('NotebookCell#Document', function () { outputs: [], }, { handle: 3, - uri: CellUri.generate(notebookUri, 3), + uri: CellUri.generate(notebookUri, 'test', 3), source: ['Hallo', 'Welt', 'Hallo Welt!'], eol: '\n', language: 'test', @@ -284,7 +284,7 @@ suite('NotebookCell#Document', function () { kind: NotebookCellsChangeType.ModelChange, changes: [[0, 0, [{ handle: 2, - uri: CellUri.generate(notebookUri, 2), + uri: CellUri.generate(notebookUri, 'test', 2), source: ['Hello', 'World', 'Hello World!'], eol: '\n', language: 'test', @@ -292,7 +292,7 @@ suite('NotebookCell#Document', function () { outputs: [], }, { handle: 3, - uri: CellUri.generate(notebookUri, 3), + uri: CellUri.generate(notebookUri, 'test', 3), source: ['Hallo', 'Welt', 'Hallo Welt!'], eol: '\n', language: 'test', diff --git a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts index e49a0c930ef415a56968e2444c70b2e0611e1894..776543c4fdac379e66be2157027cd67ef5abf75f 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts @@ -61,7 +61,7 @@ suite('NotebookConcatDocument', function () { viewType: 'test', cells: [{ handle: 0, - uri: CellUri.generate(notebookUri, 0), + uri: CellUri.generate(notebookUri, 'test', 0), source: ['### Heading'], eol: '\n', language: 'markdown', @@ -122,8 +122,8 @@ suite('NotebookConcatDocument', function () { test('contains', function () { - const cellUri1 = CellUri.generate(notebook.uri, 1); - const cellUri2 = CellUri.generate(notebook.uri, 2); + const cellUri1 = CellUri.generate(notebook.uri, 'test', 1); + const cellUri2 = CellUri.generate(notebook.uri, 'test', 2); extHostNotebooks.$acceptModelChanged(notebookUri, { versionId: notebook.notebookDocument.version + 1, @@ -169,7 +169,7 @@ suite('NotebookConcatDocument', function () { kind: NotebookCellsChangeType.ModelChange, changes: [[0, 0, [{ handle: 1, - uri: CellUri.generate(notebook.uri, 1), + uri: CellUri.generate(notebook.uri, 'test', 1), source: ['Hello', 'World', 'Hello World!'], eol: '\n', language: 'test', @@ -177,7 +177,7 @@ suite('NotebookConcatDocument', function () { outputs: [], }, { handle: 2, - uri: CellUri.generate(notebook.uri, 2), + uri: CellUri.generate(notebook.uri, 'test', 2), source: ['Hallo', 'Welt', 'Hallo Welt!'], eol: '\n', language: 'test', @@ -214,7 +214,7 @@ suite('NotebookConcatDocument', function () { kind: NotebookCellsChangeType.ModelChange, changes: [[0, 0, [{ handle: 1, - uri: CellUri.generate(notebook.uri, 1), + uri: CellUri.generate(notebook.uri, 'test', 1), source: ['Hello', 'World', 'Hello World!'], eol: '\n', language: 'test', @@ -241,7 +241,7 @@ suite('NotebookConcatDocument', function () { kind: NotebookCellsChangeType.ModelChange, changes: [[1, 0, [{ handle: 2, - uri: CellUri.generate(notebook.uri, 2), + uri: CellUri.generate(notebook.uri, 'test', 2), source: ['Hallo', 'Welt', 'Hallo Welt!'], eol: '\n', language: 'test', @@ -292,7 +292,7 @@ suite('NotebookConcatDocument', function () { kind: NotebookCellsChangeType.ModelChange, changes: [[0, 0, [{ handle: 1, - uri: CellUri.generate(notebook.uri, 1), + uri: CellUri.generate(notebook.uri, 'test', 1), source: ['Hello', 'World', 'Hello World!'], eol: '\n', language: 'test', @@ -300,7 +300,7 @@ suite('NotebookConcatDocument', function () { outputs: [], }, { handle: 2, - uri: CellUri.generate(notebook.uri, 2), + uri: CellUri.generate(notebook.uri, 'test', 2), source: ['Hallo', 'Welt', 'Hallo Welt!'], eol: '\n', language: 'test', @@ -350,7 +350,7 @@ suite('NotebookConcatDocument', function () { kind: NotebookCellsChangeType.ModelChange, changes: [[0, 0, [{ handle: 1, - uri: CellUri.generate(notebook.uri, 1), + uri: CellUri.generate(notebook.uri, 'test', 1), source: ['fooLang-document'], eol: '\n', language: 'fooLang', @@ -358,7 +358,7 @@ suite('NotebookConcatDocument', function () { outputs: [], }, { handle: 2, - uri: CellUri.generate(notebook.uri, 2), + uri: CellUri.generate(notebook.uri, 'test', 2), source: ['barLang-document'], eol: '\n', language: 'barLang', @@ -384,7 +384,7 @@ suite('NotebookConcatDocument', function () { kind: NotebookCellsChangeType.ModelChange, changes: [[2, 0, [{ handle: 3, - uri: CellUri.generate(notebook.uri, 3), + uri: CellUri.generate(notebook.uri, 'test', 3), source: ['barLang-document2'], eol: '\n', language: 'barLang', @@ -422,7 +422,7 @@ suite('NotebookConcatDocument', function () { kind: NotebookCellsChangeType.ModelChange, changes: [[0, 0, [{ handle: 1, - uri: CellUri.generate(notebook.uri, 1), + uri: CellUri.generate(notebook.uri, 'test', 1), source: ['Hello', 'World', 'Hello World!'], eol: '\n', language: 'test', @@ -430,7 +430,7 @@ suite('NotebookConcatDocument', function () { outputs: [], }, { handle: 2, - uri: CellUri.generate(notebook.uri, 2), + uri: CellUri.generate(notebook.uri, 'test', 2), source: ['Hallo', 'Welt', 'Hallo Welt!'], eol: '\n', language: 'test', @@ -479,7 +479,7 @@ suite('NotebookConcatDocument', function () { kind: NotebookCellsChangeType.ModelChange, changes: [[0, 0, [{ handle: 1, - uri: CellUri.generate(notebook.uri, 1), + uri: CellUri.generate(notebook.uri, 'test', 1), source: ['Hello', 'World', 'Hello World!'], eol: '\n', language: 'test', @@ -487,7 +487,7 @@ suite('NotebookConcatDocument', function () { outputs: [], }, { handle: 2, - uri: CellUri.generate(notebook.uri, 2), + uri: CellUri.generate(notebook.uri, 'test', 2), source: ['Hallo', 'Welt', 'Hallo Welt!'], eol: '\n', language: 'test', @@ -520,7 +520,7 @@ suite('NotebookConcatDocument', function () { kind: NotebookCellsChangeType.ModelChange, changes: [[0, 0, [{ handle: 1, - uri: CellUri.generate(notebook.uri, 1), + uri: CellUri.generate(notebook.uri, 'test', 1), source: ['Hello', 'World', 'Hello World!'], eol: '\n', language: 'test', @@ -528,7 +528,7 @@ suite('NotebookConcatDocument', function () { outputs: [], }, { handle: 2, - uri: CellUri.generate(notebook.uri, 2), + uri: CellUri.generate(notebook.uri, 'test', 2), source: ['Hallo', 'Welt', 'Hallo Welt!'], eol: '\n', language: 'test', @@ -558,7 +558,7 @@ suite('NotebookConcatDocument', function () { kind: NotebookCellsChangeType.ModelChange, changes: [[0, 0, [{ handle: 1, - uri: CellUri.generate(notebook.uri, 1), + uri: CellUri.generate(notebook.uri, 'test', 1), source: ['Hello', 'World', 'Hello World!'], eol: '\n', language: 'test', @@ -566,7 +566,7 @@ suite('NotebookConcatDocument', function () { outputs: [], }, { handle: 2, - uri: CellUri.generate(notebook.uri, 2), + uri: CellUri.generate(notebook.uri, 'test', 2), source: ['Hallo', 'Welt', 'Hallo Welt!'], eol: '\n', language: 'test',