From 951a21a394dab33e0a8db9b78df4005517eafd94 Mon Sep 17 00:00:00 2001 From: Chapman Pendery Date: Tue, 16 Apr 2024 14:38:08 -0700 Subject: [PATCH 1/2] fix: split fails in git bash Signed-off-by: Chapman Pendery --- .../terminal/browser/media/shellIntegration-bash.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh index 4d8ce3355f6..55cf9adf6ed 100755 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh @@ -137,6 +137,9 @@ __vsc_escape_value() { # Send the IsWindows property if the environment looks like Windows if [[ "$(uname -s)" =~ ^CYGWIN*|MINGW*|MSYS* ]]; then builtin printf '\e]633;P;IsWindows=True\a' + __vsc_is_windows=1 +else + __vsc_is_windows=0 fi # Allow verifying $BASH_COMMAND doesn't have aliases resolved via history when the right HISTCONTROL @@ -168,7 +171,12 @@ __vsc_prompt_end() { } __vsc_update_cwd() { - builtin printf '\e]633;P;Cwd=%s\a' "$(__vsc_escape_value "$PWD")" + if [ "$__vsc_is_windows" = "1" ]; then + __vsc_cwd="$(cygpath -m "$PWD")" + else + __vsc_cwd="$PWD" + fi + builtin printf '\e]633;P;Cwd=%s\a' "$(__vsc_escape_value "$__vsc_cwd")" } __vsc_command_output_start() { From 910284865c34fd492e3f6fd5a8ab8e571432cc58 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 17 Apr 2024 12:51:23 +1000 Subject: [PATCH 2/2] Cache outline headers & ref count outline provider (#210213) * Cache outline headers & ref count outline provider * Fix tests * Remove handle * Oops * Simpler cachine --- .../contrib/outline/notebookOutline.ts | 31 +++---- .../notebook/browser/notebook.contribution.ts | 2 + .../notebook/browser/notebookBrowser.ts | 1 + .../notebook/browser/notebookEditorWidget.ts | 7 +- .../browser/viewModel/baseCellViewModel.ts | 4 + .../viewModel/notebookOutlineEntryFactory.ts | 34 +++++--- .../viewModel/notebookOutlineProvider.ts | 84 ++++++++++--------- .../notebookOutlineProviderFactory.ts | 39 +++++++++ .../viewParts/notebookEditorStickyScroll.ts | 30 ++++--- .../test/browser/testNotebookEditor.ts | 5 +- 10 files changed, 150 insertions(+), 87 deletions(-) create mode 100644 src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProviderFactory.ts diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts index 84c4f4a6782..bec937985d8 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts @@ -12,7 +12,7 @@ import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { Emitter, Event } from 'vs/base/common/event'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; -import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, toDisposable, type IReference } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { getIconClassesForLanguageId } from 'vs/editor/common/services/getIconClasses'; @@ -51,6 +51,7 @@ import { IOutlinePane } from 'vs/workbench/contrib/outline/browser/outline'; import { Codicon } from 'vs/base/common/codicons'; import { NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { NotebookOutlineConstants } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory'; +import { INotebookCellOutlineProviderFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProviderFactory'; class NotebookOutlineTemplate { @@ -337,7 +338,7 @@ export class NotebookCellOutline implements IOutline { readonly onDidChange: Event = this._onDidChange.event; get entries(): OutlineEntry[] { - return this._outlineProvider?.entries ?? []; + return this._outlineProviderReference?.object?.entries ?? []; } private readonly _entriesDisposables = new DisposableStore(); @@ -347,10 +348,10 @@ export class NotebookCellOutline implements IOutline { readonly outlineKind = 'notebookCells'; get activeElement(): OutlineEntry | undefined { - return this._outlineProvider?.activeElement; + return this._outlineProviderReference?.object?.activeElement; } - private _outlineProvider: NotebookCellOutlineProvider | undefined; + private _outlineProviderReference: IReference | undefined; private readonly _localDisposables = new DisposableStore(); constructor( @@ -363,14 +364,14 @@ export class NotebookCellOutline implements IOutline { const installSelectionListener = () => { const notebookEditor = _editor.getControl(); if (!notebookEditor?.hasModel()) { - this._outlineProvider?.dispose(); - this._outlineProvider = undefined; + this._outlineProviderReference?.dispose(); + this._outlineProviderReference = undefined; this._localDisposables.clear(); } else { - this._outlineProvider?.dispose(); + this._outlineProviderReference?.dispose(); this._localDisposables.clear(); - this._outlineProvider = instantiationService.createInstance(NotebookCellOutlineProvider, notebookEditor, _target); - this._localDisposables.add(this._outlineProvider.onDidChange(e => { + this._outlineProviderReference = instantiationService.invokeFunction((accessor) => accessor.get(INotebookCellOutlineProviderFactory).getOrCreate(notebookEditor, _target)); + this._localDisposables.add(this._outlineProviderReference.object.onDidChange(e => { this._onDidChange.fire(e); })); } @@ -411,7 +412,7 @@ export class NotebookCellOutline implements IOutline { return result; } }, - quickPickDataSource: instantiationService.createInstance(NotebookQuickPickProvider, () => (this._outlineProvider?.entries ?? [])), + quickPickDataSource: instantiationService.createInstance(NotebookQuickPickProvider, () => (this._outlineProviderReference?.object?.entries ?? [])), treeDataSource, delegate, renderers, @@ -425,7 +426,7 @@ export class NotebookCellOutline implements IOutline { const showCodeCellSymbols = configurationService.getValue(NotebookSetting.outlineShowCodeCellSymbols); const showMarkdownHeadersOnly = configurationService.getValue(NotebookSetting.outlineShowMarkdownHeadersOnly); - for (const entry of parent instanceof NotebookCellOutline ? (this._outlineProvider?.entries ?? []) : parent.children) { + for (const entry of parent instanceof NotebookCellOutline ? (this._outlineProviderReference?.object?.entries ?? []) : parent.children) { if (entry.cell.cellKind === CellKind.Markup) { if (!showMarkdownHeadersOnly) { yield entry; @@ -444,14 +445,14 @@ export class NotebookCellOutline implements IOutline { } async setFullSymbols(cancelToken: CancellationToken) { - await this._outlineProvider?.setFullSymbols(cancelToken); + await this._outlineProviderReference?.object?.setFullSymbols(cancelToken); } get uri(): URI | undefined { - return this._outlineProvider?.uri; + return this._outlineProviderReference?.object?.uri; } get isEmpty(): boolean { - return this._outlineProvider?.isEmpty ?? true; + return this._outlineProviderReference?.object?.isEmpty ?? true; } async reveal(entry: OutlineEntry, options: IEditorOptions, sideBySide: boolean): Promise { await this._editorService.openEditor({ @@ -530,7 +531,7 @@ export class NotebookCellOutline implements IOutline { this._onDidChange.dispose(); this._dispoables.dispose(); this._entriesDisposables.dispose(); - this._outlineProvider?.dispose(); + this._outlineProviderReference?.dispose(); this._localDisposables.dispose(); } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 30866bc8fd4..650e0bf5074 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -57,6 +57,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { NotebookRendererMessagingService } from 'vs/workbench/contrib/notebook/browser/services/notebookRendererMessagingServiceImpl'; import { INotebookRendererMessagingService } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService'; +import { INotebookCellOutlineProviderFactory, NotebookCellOutlineProviderFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProviderFactory'; // Editor Controller import 'vs/workbench/contrib/notebook/browser/controller/coreActions'; @@ -755,6 +756,7 @@ registerSingleton(INotebookExecutionStateService, NotebookExecutionStateService, registerSingleton(INotebookRendererMessagingService, NotebookRendererMessagingService, InstantiationType.Delayed); registerSingleton(INotebookKeymapService, NotebookKeymapService, InstantiationType.Delayed); registerSingleton(INotebookLoggingService, NotebookLoggingService, InstantiationType.Delayed); +registerSingleton(INotebookCellOutlineProviderFactory, NotebookCellOutlineProviderFactory, InstantiationType.Delayed); const schemas: IJSONSchemaMap = {}; function isConfigurationPropertySchema(x: IConfigurationPropertySchema | { [path: string]: IConfigurationPropertySchema }): x is IConfigurationPropertySchema { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 35eb6e5800e..ce34e79961e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -260,6 +260,7 @@ export interface ICellViewModel extends IGenericCellViewModel { focusedOutputId?: string | undefined; outputIsHovered: boolean; getText(): string; + getAlternativeId(): number; getTextLength(): number; getHeight(lineHeight: number): number; metadata: NotebookCellMetadata; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 0eaf57d26a5..e3d523ed3bb 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -96,10 +96,8 @@ import { Schemas } from 'vs/base/common/network'; import { DropIntoEditorController } from 'vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController'; import { CopyPasteController } from 'vs/editor/contrib/dropOrPasteInto/browser/copyPasteController'; import { NotebookStickyScroll } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll'; -import { NotebookCellOutlineProvider } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; import { PixelRatio } from 'vs/base/browser/pixelRatio'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { PreventDefaultContextMenuItemsContextKeyName } from 'vs/workbench/contrib/webview/browser/webview.contribution'; @@ -278,7 +276,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD public readonly scopedContextKeyService: IContextKeyService; private readonly instantiationService: IInstantiationService; private readonly _notebookOptions: NotebookOptions; - public readonly _notebookOutline: NotebookCellOutlineProvider; private _currentProgress: IProgressRunner | undefined; @@ -335,8 +332,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._register(this.instantiationService.createInstance(NotebookEditorContextKeys, this)); - this._notebookOutline = this._register(this.instantiationService.createInstance(NotebookCellOutlineProvider, this, OutlineTarget.QuickPick)); - this._register(notebookKernelService.onDidChangeSelectedNotebooks(e => { if (isEqual(e.notebook, this.viewModel?.uri)) { this._loadKernelPreloads(); @@ -1054,7 +1049,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } private _registerNotebookStickyScroll() { - this._notebookStickyScroll = this._register(this.instantiationService.createInstance(NotebookStickyScroll, this._notebookStickyScrollContainer, this, this._notebookOutline, this._list)); + this._notebookStickyScroll = this._register(this.instantiationService.createInstance(NotebookStickyScroll, this._notebookStickyScrollContainer, this, this._list)); const localDisposableStore = this._register(new DisposableStore()); diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts index a8d2b94c781..09c281a2604 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -307,6 +307,10 @@ export abstract class BaseCellViewModel extends Disposable { return this.model.getValue(); } + getAlternativeId(): number { + return this.model.alternativeId; + } + getTextLength(): number { return this.model.getTextLength(); } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory.ts index 5cf8961798c..ded82f21617 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory.ts @@ -27,10 +27,25 @@ type entryDesc = { kind: SymbolKind; }; +function getMarkdownHeadersInCellFallbackToHtmlTags(fullContent: string) { + const headers = Array.from(getMarkdownHeadersInCell(fullContent)); + if (headers.length) { + return headers; + } + // no markdown syntax headers, try to find html tags + const match = fullContent.match(/(.*)<\/h\1>/i); + if (match) { + const level = parseInt(match[1]); + const text = match[2].trim(); + headers.push({ depth: level, text }); + } + return headers; +} + export class NotebookOutlineEntryFactory { private cellOutlineEntryCache: Record = {}; - + private readonly cachedMarkdownOutlineEntries = new WeakMap(); constructor( private readonly executionStateService: INotebookExecutionStateService ) { } @@ -48,22 +63,15 @@ export class NotebookOutlineEntryFactory { if (isMarkdown) { const fullContent = cell.getText().substring(0, 10000); - for (const { depth, text } of getMarkdownHeadersInCell(fullContent)) { + const cache = this.cachedMarkdownOutlineEntries.get(cell); + const headers = cache?.alternativeId === cell.getAlternativeId() ? cache.headers : Array.from(getMarkdownHeadersInCellFallbackToHtmlTags(fullContent)); + this.cachedMarkdownOutlineEntries.set(cell, { alternativeId: cell.getAlternativeId(), headers }); + + for (const { depth, text } of headers) { hasHeader = true; entries.push(new OutlineEntry(index++, depth, cell, text, false, false)); } - if (!hasHeader) { - // no markdown syntax headers, try to find html tags - const match = fullContent.match(/(.*)<\/h\1>/i); - if (match) { - hasHeader = true; - const level = parseInt(match[1]); - const text = match[2].trim(); - entries.push(new OutlineEntry(index++, level, cell, text, false, false)); - } - } - if (!hasHeader) { content = renderMarkdownAsPlaintext({ value: content }); } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider.ts index 927d67ba143..40f36495e06 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider.ts @@ -4,20 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore, MutableDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IMarkerService } from 'vs/platform/markers/common/markers'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IActiveNotebookEditor, ICellViewModel, INotebookEditor, INotebookViewCellsUpdateEvent } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookExecutionStateService, NotebookExecutionType, type ICellExecutionStateChangedEvent, type IExecutionStateChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; +import { IActiveNotebookEditor, ICellViewModel, INotebookEditor, type INotebookViewCellsUpdateEvent } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellKind, NotebookCellsChangeType, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { OutlineChangeEvent, OutlineConfigKeys, OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; import { OutlineEntry } from './OutlineEntry'; import { IOutlineModelService } from 'vs/editor/contrib/documentSymbols/browser/outlineModel'; import { CancellationToken } from 'vs/base/common/cancellation'; import { NotebookOutlineConstants, NotebookOutlineEntryFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory'; +import { Delayer } from 'vs/base/common/async'; export class NotebookCellOutlineProvider { private readonly _disposables = new DisposableStore(); @@ -41,7 +42,6 @@ export class NotebookCellOutlineProvider { } private readonly _outlineEntryFactory: NotebookOutlineEntryFactory; - constructor( private readonly _editor: INotebookEditor, private readonly _target: OutlineTarget, @@ -53,29 +53,34 @@ export class NotebookCellOutlineProvider { ) { this._outlineEntryFactory = new NotebookOutlineEntryFactory(notebookExecutionStateService); - const selectionListener = new MutableDisposable(); - this._disposables.add(selectionListener); - - selectionListener.value = combinedDisposable( - Event.debounce( - _editor.onDidChangeSelection, - (last, _current) => last, - 200 - )(this._recomputeActive, this), - Event.debounce( - _editor.onDidChangeViewCells, - (last, _current) => last ?? _current, - 200 - )(this._recomputeState, this) + this._disposables.add(Event.debounce( + _editor.onDidChangeSelection, + (last, _current) => last, + 200 + )(() => { + this._recomputeActive(); + }, this)) + this._disposables.add(Event.debounce( + _editor.onDidChangeViewCells, + (last, _current) => last ?? _current, + 200 + )(() => { + this._recomputeActive(); + }, this) ); + // .3s of a delay is sufficient, 100-200s is too quick and will unnecessarily block the ui thread. + // Given we're only updating the outline when the user types, we can afford to wait a bit. + const delayer = this._disposables.add(new Delayer(300)); + const delayedRecompute = () => delayer.trigger(() => this._recomputeState()); + this._disposables.add(_configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(NotebookSetting.outlineShowMarkdownHeadersOnly) || e.affectsConfiguration(NotebookSetting.outlineShowCodeCells) || e.affectsConfiguration(NotebookSetting.outlineShowCodeCellSymbols) || e.affectsConfiguration(NotebookSetting.breadcrumbsShowCodeCells) ) { - this._recomputeState(); + delayedRecompute(); } })); @@ -84,17 +89,28 @@ export class NotebookCellOutlineProvider { })); this._disposables.add( - Event.debounce( - notebookExecutionStateService.onDidChangeExecution, - (last, _current) => last ?? _current, - 200)(e => { - if (e.type === NotebookExecutionType.cell && !!this._editor.textModel && e.affectsNotebook(this._editor.textModel?.uri)) { - this._recomputeState(); - } - }) + notebookExecutionStateService.onDidChangeExecution(e => { + if (e.type === NotebookExecutionType.cell && !!this._editor.textModel && e.affectsNotebook(this._editor.textModel?.uri)) { + delayedRecompute(); + } + }) ); - this._recomputeState(); + const disposable = this._disposables.add(new DisposableStore()); + const monitorModelChanges = () => { + disposable.clear(); + if (!this._editor.textModel) { + return; + } + disposable.add(this._editor.textModel.onDidChangeContent(contentChanges => { + if (contentChanges.rawEvents.some(c => c.kind === NotebookCellsChangeType.ChangeCellContent)) { + delayedRecompute(); + } + })); + } + this._disposables.add(this._editor.onDidChangeModel(monitorModelChanges)); + monitorModelChanges(); + this._recomputeState() } dispose(): void { @@ -104,10 +120,6 @@ export class NotebookCellOutlineProvider { this._disposables.dispose(); } - init(): void { - this._recomputeState(); - } - async setFullSymbols(cancelToken: CancellationToken) { const notebookEditorWidget = this._editor; @@ -126,7 +138,6 @@ export class NotebookCellOutlineProvider { this._recomputeState(); } - private _recomputeState(): void { this._entriesDisposables.clear(); this._activeEntry = undefined; @@ -159,11 +170,6 @@ export class NotebookCellOutlineProvider { const entries: OutlineEntry[] = []; for (const cell of notebookCells) { entries.push(...this._outlineEntryFactory.getOutlineEntries(cell, this._target, entries.length)); - // send an event whenever any of the cells change - this._entriesDisposables.add(cell.model.onDidChangeContent(() => { - this._recomputeState(); - this._onDidChange.fire({}); - })); } // build a tree from the list of entries diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProviderFactory.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProviderFactory.ts new file mode 100644 index 00000000000..d5908204c94 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProviderFactory.ts @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ReferenceCollection, type IReference } from 'vs/base/common/lifecycle'; +import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import type { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookCellOutlineProvider } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider'; +import type { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; + +class NotebookCellOutlineProviderReferenceCollection extends ReferenceCollection { + constructor(@IInstantiationService private readonly instantiationService: IInstantiationService) { + super(); + } + protected override createReferencedObject(_key: string, editor: INotebookEditor, target: OutlineTarget): NotebookCellOutlineProvider { + return this.instantiationService.createInstance(NotebookCellOutlineProvider, editor, target); + } + protected override destroyReferencedObject(_key: string, object: NotebookCellOutlineProvider): void { + object.dispose(); + } +} + +export const INotebookCellOutlineProviderFactory = createDecorator('INotebookCellOutlineProviderFactory'); + +export interface INotebookCellOutlineProviderFactory { + getOrCreate(editor: INotebookEditor, target: OutlineTarget): IReference +} + +export class NotebookCellOutlineProviderFactory implements INotebookCellOutlineProviderFactory { + private readonly _data: NotebookCellOutlineProviderReferenceCollection; + constructor(@IInstantiationService instantiationService: IInstantiationService) { + this._data = instantiationService.createInstance(NotebookCellOutlineProviderReferenceCollection); + } + + getOrCreate(editor: INotebookEditor, target: OutlineTarget): IReference { + return this._data.acquire(editor.getId(), editor, target); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts index 5900460feb9..521b8d908f7 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts @@ -7,7 +7,7 @@ import * as DOM from 'vs/base/browser/dom'; import { EventType as TouchEventType } from 'vs/base/browser/touch'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, type IReference } from 'vs/base/common/lifecycle'; import { MenuId } from 'vs/platform/actions/common/actions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { CellFoldingState, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -22,6 +22,9 @@ import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewM import { FoldingController } from 'vs/workbench/contrib/notebook/browser/controller/foldingController'; import { NotebookOptionsChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; import { NotebookSectionArgs } from 'vs/workbench/contrib/notebook/browser/controller/sectionActions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { INotebookCellOutlineProviderFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProviderFactory'; +import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; export class NotebookStickyLine extends Disposable { constructor( @@ -102,6 +105,7 @@ export class NotebookStickyScroll extends Disposable { private readonly _onDidChangeNotebookStickyScroll = this._register(new Emitter()); readonly onDidChangeNotebookStickyScroll: Event = this._onDidChangeNotebookStickyScroll.event; + private notebookOutlineReference?: IReference; getDomNode(): HTMLElement { return this.domNode; @@ -139,9 +143,9 @@ export class NotebookStickyScroll extends Disposable { constructor( private readonly domNode: HTMLElement, private readonly notebookEditor: INotebookEditor, - private readonly notebookOutline: NotebookCellOutlineProvider, private readonly notebookCellList: INotebookCellList, @IContextMenuService private readonly _contextMenuService: IContextMenuService, + @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); @@ -187,37 +191,37 @@ export class NotebookStickyScroll extends Disposable { this.init(); } else { this._disposables.clear(); - this.notebookOutline.dispose(); + this.notebookOutlineReference?.dispose(); this.disposeCurrentStickyLines(); DOM.clearNode(this.domNode); this.updateDisplay(); } - } else if (e.stickyScrollMode && this.notebookEditor.notebookOptions.getDisplayOptions().stickyScrollEnabled) { - this.updateContent(computeContent(this.notebookEditor, this.notebookCellList, this.notebookOutline.entries, this.getCurrentStickyHeight())); + } else if (e.stickyScrollMode && this.notebookEditor.notebookOptions.getDisplayOptions().stickyScrollEnabled && this.notebookOutlineReference?.object) { + this.updateContent(computeContent(this.notebookEditor, this.notebookCellList, this.notebookOutlineReference?.object?.entries, this.getCurrentStickyHeight())); } } private init() { - this.notebookOutline.init(); - this.updateContent(computeContent(this.notebookEditor, this.notebookCellList, this.notebookOutline.entries, this.getCurrentStickyHeight())); + const { object: notebookOutlineReference } = this.notebookOutlineReference = this.instantiationService.invokeFunction((accessor) => accessor.get(INotebookCellOutlineProviderFactory).getOrCreate(this.notebookEditor, OutlineTarget.QuickPick)); + this._register(this.notebookOutlineReference); + this.updateContent(computeContent(this.notebookEditor, this.notebookCellList, notebookOutlineReference.entries, this.getCurrentStickyHeight())); - this._disposables.add(this.notebookOutline.onDidChange(() => { - const recompute = computeContent(this.notebookEditor, this.notebookCellList, this.notebookOutline.entries, this.getCurrentStickyHeight()); + this._disposables.add(notebookOutlineReference.onDidChange(() => { + const recompute = computeContent(this.notebookEditor, this.notebookCellList, notebookOutlineReference.entries, this.getCurrentStickyHeight()); if (!this.compareStickyLineMaps(recompute, this.currentStickyLines)) { this.updateContent(recompute); } })); this._disposables.add(this.notebookEditor.onDidAttachViewModel(() => { - this.notebookOutline.init(); - this.updateContent(computeContent(this.notebookEditor, this.notebookCellList, this.notebookOutline.entries, this.getCurrentStickyHeight())); + this.updateContent(computeContent(this.notebookEditor, this.notebookCellList, notebookOutlineReference.entries, this.getCurrentStickyHeight())); })); this._disposables.add(this.notebookEditor.onDidScroll(() => { const d = new Delayer(100); d.trigger(() => { d.dispose(); - const recompute = computeContent(this.notebookEditor, this.notebookCellList, this.notebookOutline.entries, this.getCurrentStickyHeight()); + const recompute = computeContent(this.notebookEditor, this.notebookCellList, notebookOutlineReference.entries, this.getCurrentStickyHeight()); if (!this.compareStickyLineMaps(recompute, this.currentStickyLines)) { this.updateContent(recompute); } @@ -365,7 +369,7 @@ export class NotebookStickyScroll extends Disposable { override dispose() { this._disposables.dispose(); this.disposeCurrentStickyLines(); - this.notebookOutline.dispose(); + this.notebookOutlineReference?.dispose(); super.dispose(); } } diff --git a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts index 228e0bb1fdf..2404be29f8e 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts @@ -67,6 +67,7 @@ import { mainWindow } from 'vs/base/browser/window'; import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices'; import { IInlineChatService } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl'; +import { INotebookCellOutlineProviderFactory, NotebookCellOutlineProviderFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProviderFactory'; export class TestCell extends NotebookCellTextModel { constructor( @@ -200,6 +201,7 @@ export function setupInstantiationService(disposables: DisposableStore) { instantiationService.stub(INotebookCellStatusBarService, disposables.add(new NotebookCellStatusBarService())); instantiationService.stub(ICodeEditorService, disposables.add(new TestCodeEditorService(testThemeService))); instantiationService.stub(IInlineChatService, instantiationService.createInstance(InlineChatServiceImpl)); + instantiationService.stub(INotebookCellOutlineProviderFactory, instantiationService.createInstance(NotebookCellOutlineProviderFactory)); return instantiationService; } @@ -228,6 +230,7 @@ function _createTestNotebookEditor(instantiationService: TestInstantiationServic let visibleRanges: ICellRange[] = [{ start: 0, end: 100 }]; + const id = Date.now().toString(); const notebookEditor: IActiveNotebookEditorDelegate = new class extends mock() { // eslint-disable-next-line local/code-must-use-super-dispose override dispose() { @@ -313,7 +316,7 @@ function _createTestNotebookEditor(instantiationService: TestInstantiationServic visibleRanges = _ranges; } - override getId(): string { return ''; } + override getId(): string { return id; } override setScrollTop(scrollTop: number): void { cellList.scrollTop = scrollTop; }