Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor NotebookCellOutline layers to remove target + breadcrumb fix #212002

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

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