Skip to content

Commit

Permalink
Refactor NotebookCellOutline layers to remove target + breadcrumb…
Browse files Browse the repository at this point in the history
… fix (#212002)

* shift outline filters to fix breadcrumbs

* viewProviders only take outlineDataSourceRef + remove target + shrink nbOutlineCreator

* listeners -> `NotebookOutline` + symbols on symbolProvider change + cache settings

* remove unused code

* fix service mocks

* uh oh WE GOT A LEAK (its ok i am a plumber)

* more test repair
  • Loading branch information
Yoyokrazy committed Jun 12, 2024
1 parent c095702 commit a4ac06f
Show file tree
Hide file tree
Showing 13 changed files with 646 additions and 495 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +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';
import { INotebookCellOutlineDataSourceFactory, NotebookCellOutlineDataSourceFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineDataSourceFactory';

// Editor Controller
import 'vs/workbench/contrib/notebook/browser/controller/coreActions';
Expand Down Expand Up @@ -726,7 +726,7 @@ registerSingleton(INotebookExecutionStateService, NotebookExecutionStateService,
registerSingleton(INotebookRendererMessagingService, NotebookRendererMessagingService, InstantiationType.Delayed);
registerSingleton(INotebookKeymapService, NotebookKeymapService, InstantiationType.Delayed);
registerSingleton(INotebookLoggingService, NotebookLoggingService, InstantiationType.Delayed);
registerSingleton(INotebookCellOutlineProviderFactory, NotebookCellOutlineProviderFactory, InstantiationType.Delayed);
registerSingleton(INotebookCellOutlineDataSourceFactory, NotebookCellOutlineDataSourceFactory, InstantiationType.Delayed);

const schemas: IJSONSchemaMap = {};
function isConfigurationPropertySchema(x: IConfigurationPropertySchema | { [path: string]: IConfigurationPropertySchema }): x is IConfigurationPropertySchema {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Emitter, Event } from 'vs/base/common/event';
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 { IActiveNotebookEditor, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { OutlineChangeEvent, OutlineConfigKeys } 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 { NotebookOutlineEntryFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory';

export interface INotebookCellOutlineDataSource {
readonly activeElement: OutlineEntry | undefined;
readonly entries: OutlineEntry[];
}

export class NotebookCellOutlineDataSource implements INotebookCellOutlineDataSource {

private readonly _disposables = new DisposableStore();

private readonly _onDidChange = new Emitter<OutlineChangeEvent>();
readonly onDidChange: Event<OutlineChangeEvent> = this._onDidChange.event;

private _uri: URI | undefined;
private _entries: OutlineEntry[] = [];
private _activeEntry?: OutlineEntry;

private readonly _outlineEntryFactory: NotebookOutlineEntryFactory;

constructor(
private readonly _editor: INotebookEditor,
@INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService,
@IOutlineModelService private readonly _outlineModelService: IOutlineModelService,
@IMarkerService private readonly _markerService: IMarkerService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
) {
this._outlineEntryFactory = new NotebookOutlineEntryFactory(this._notebookExecutionStateService);
this.recomputeState();
}

get activeElement(): OutlineEntry | undefined {
return this._activeEntry;
}
get entries(): OutlineEntry[] {
return this._entries;
}
get isEmpty(): boolean {
return this._entries.length === 0;
}
get uri() {
return this._uri;
}

public async computeFullSymbols(cancelToken: CancellationToken) {
const notebookEditorWidget = this._editor;

const notebookCells = notebookEditorWidget?.getViewModel()?.viewCells.filter((cell) => cell.cellKind === CellKind.Code);

if (notebookCells) {
const promises: Promise<void>[] = [];
// limit the number of cells so that we don't resolve an excessive amount of text models
for (const cell of notebookCells.slice(0, 100)) {
// gather all symbols asynchronously
promises.push(this._outlineEntryFactory.cacheSymbols(cell, this._outlineModelService, cancelToken));
}
await Promise.allSettled(promises);
}
this.recomputeState();
}

public recomputeState(): void {
this._disposables.clear();
this._activeEntry = undefined;
this._uri = undefined;

if (!this._editor.hasModel()) {
return;
}

this._uri = this._editor.textModel.uri;

const notebookEditorWidget: IActiveNotebookEditor = this._editor;

if (notebookEditorWidget.getLength() === 0) {
return;
}

const notebookCells = notebookEditorWidget.getViewModel().viewCells;

const entries: OutlineEntry[] = [];
for (const cell of notebookCells) {
entries.push(...this._outlineEntryFactory.getOutlineEntries(cell, entries.length));
}

// build a tree from the list of entries
if (entries.length > 0) {
const result: OutlineEntry[] = [entries[0]];
const parentStack: OutlineEntry[] = [entries[0]];

for (let i = 1; i < entries.length; i++) {
const entry = entries[i];

while (true) {
const len = parentStack.length;
if (len === 0) {
// root node
result.push(entry);
parentStack.push(entry);
break;

} else {
const parentCandidate = parentStack[len - 1];
if (parentCandidate.level < entry.level) {
parentCandidate.addChild(entry);
parentStack.push(entry);
break;
} else {
parentStack.pop();
}
}
}
}
this._entries = result;
}

// feature: show markers with each cell
const markerServiceListener = new MutableDisposable();
this._disposables.add(markerServiceListener);
const updateMarkerUpdater = () => {
if (notebookEditorWidget.isDisposed) {
return;
}

const doUpdateMarker = (clear: boolean) => {
for (const entry of this._entries) {
if (clear) {
entry.clearMarkers();
} else {
entry.updateMarkers(this._markerService);
}
}
};
const problem = this._configurationService.getValue('problems.visibility');
if (problem === undefined) {
return;
}

const config = this._configurationService.getValue(OutlineConfigKeys.problemsEnabled);

if (problem && config) {
markerServiceListener.value = this._markerService.onMarkerChanged(e => {
if (notebookEditorWidget.isDisposed) {
console.error('notebook editor is disposed');
return;
}

if (e.some(uri => notebookEditorWidget.getCellsInRange().some(cell => isEqual(cell.uri, uri)))) {
doUpdateMarker(false);
this._onDidChange.fire({});
}
});
doUpdateMarker(false);
} else {
markerServiceListener.clear();
doUpdateMarker(true);
}
};
updateMarkerUpdater();
this._disposables.add(this._configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('problems.visibility') || e.affectsConfiguration(OutlineConfigKeys.problemsEnabled)) {
updateMarkerUpdater();
this._onDidChange.fire({});
}
}));

const { changeEventTriggered } = this.recomputeActive();
if (!changeEventTriggered) {
this._onDidChange.fire({});
}
}

public recomputeActive(): { changeEventTriggered: boolean } {
let newActive: OutlineEntry | undefined;
const notebookEditorWidget = this._editor;

if (notebookEditorWidget) {//TODO don't check for widget, only here if we do have
if (notebookEditorWidget.hasModel() && notebookEditorWidget.getLength() > 0) {
const cell = notebookEditorWidget.cellAt(notebookEditorWidget.getFocus().start);
if (cell) {
for (const entry of this._entries) {
newActive = entry.find(cell, []);
if (newActive) {
break;
}
}
}
}
}

if (newActive !== this._activeEntry) {
this._activeEntry = newActive;
this._onDidChange.fire({ affectOnlyActiveElement: true });
return { changeEventTriggered: true };
}
return { changeEventTriggered: false };
}

dispose(): void {
this._entries.length = 0;
this._activeEntry = undefined;
this._disposables.dispose();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* 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 { NotebookCellOutlineDataSource } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineDataSource';

class NotebookCellOutlineDataSourceReferenceCollection extends ReferenceCollection<NotebookCellOutlineDataSource> {
constructor(@IInstantiationService private readonly instantiationService: IInstantiationService) {
super();
}
protected override createReferencedObject(_key: string, editor: INotebookEditor): NotebookCellOutlineDataSource {
return this.instantiationService.createInstance(NotebookCellOutlineDataSource, editor);
}
protected override destroyReferencedObject(_key: string, object: NotebookCellOutlineDataSource): void {
object.dispose();
}
}

export const INotebookCellOutlineDataSourceFactory = createDecorator<INotebookCellOutlineDataSourceFactory>('INotebookCellOutlineDataSourceFactory');

export interface INotebookCellOutlineDataSourceFactory {
getOrCreate(editor: INotebookEditor): IReference<NotebookCellOutlineDataSource>;
}

export class NotebookCellOutlineDataSourceFactory implements INotebookCellOutlineDataSourceFactory {
private readonly _data: NotebookCellOutlineDataSourceReferenceCollection;
constructor(@IInstantiationService instantiationService: IInstantiationService) {
this._data = instantiationService.createInstance(NotebookCellOutlineDataSourceReferenceCollection);
}

getOrCreate(editor: INotebookEditor): IReference<NotebookCellOutlineDataSource> {
return this._data.acquire(editor.getId(), editor);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { IRange } from 'vs/editor/common/core/range';
import { SymbolKind } from 'vs/editor/common/languages';
import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline';

export const enum NotebookOutlineConstants {
NonHeaderOutlineLevel = 7,
Expand Down Expand Up @@ -50,7 +49,7 @@ export class NotebookOutlineEntryFactory {
private readonly executionStateService: INotebookExecutionStateService
) { }

public getOutlineEntries(cell: ICellViewModel, target: OutlineTarget, index: number): OutlineEntry[] {
public getOutlineEntries(cell: ICellViewModel, index: number): OutlineEntry[] {
const entries: OutlineEntry[] = [];

const isMarkdown = cell.cellKind === CellKind.Markup;
Expand Down
Loading

0 comments on commit a4ac06f

Please sign in to comment.