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
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr
import { IEditorPane } from 'vs/workbench/common/editor';
import { CellFoldingState, CellRevealType, ICellModelDecorations, ICellModelDeltaDecorations, ICellViewModel, INotebookEditor, INotebookEditorOptions, INotebookEditorPane, INotebookViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor';
import { NotebookCellOutlineProvider } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider';
import { INotebookCellOutlineDataSource, NotebookCellOutlineDataSource } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineDataSource';
import { CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
Expand All @@ -51,7 +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';
import { INotebookCellOutlineDataSourceFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineDataSourceFactory';

class NotebookOutlineTemplate {

Expand Down Expand Up @@ -287,14 +287,14 @@ class NotebookOutlineVirtualDelegate implements IListVirtualDelegate<OutlineEntr
export class NotebookQuickPickProvider implements IQuickPickDataSource<OutlineEntry> {

constructor(
private _getEntries: () => OutlineEntry[],
private readonly notebookCellOutlineDataSourceRef: IReference<INotebookCellOutlineDataSource> | undefined,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IThemeService private readonly _themeService: IThemeService
) { }

getQuickPickElements(): IQuickPickOutlineElement<OutlineEntry>[] {
const bucket: OutlineEntry[] = [];
for (const entry of this._getEntries()) {
for (const entry of this.notebookCellOutlineDataSourceRef?.object?.entries ?? []) {
entry.asFlatList(bucket);
}
const result: IQuickPickOutlineElement<OutlineEntry>[] = [];
Expand Down Expand Up @@ -334,17 +334,64 @@ export class NotebookQuickPickProvider implements IQuickPickDataSource<OutlineEn

export class NotebookOutlinePaneProvider implements IDataSource<NotebookCellOutline, OutlineEntry> {
constructor(
private _getEntries: () => OutlineEntry[],
private readonly outlineDataSourceRef: IReference<INotebookCellOutlineDataSource> | undefined,
@IConfigurationService private readonly _configurationService: IConfigurationService,
) { }

public getActiveEntry(): OutlineEntry | undefined {
const newActive = this.outlineDataSourceRef?.object?.activeElement;
if (!newActive) {
return undefined;
}

if (!this.filterEntry(newActive)) {
return newActive;
}

// find a valid parent
let parent = newActive.parent;
while (parent) {
if (this.filterEntry(parent)) {
Yoyokrazy marked this conversation as resolved.
Show resolved Hide resolved
parent = parent.parent;
} else {
return parent;
}
}

// no valid parent found, return undefined
return undefined;
}

/**
* Checks if the given outline entry should be filtered out of the outlinePane
*
* @param entry the OutlineEntry to check
* @returns true if the entry should be filtered out of the outlinePane
*/
private filterEntry(entry: OutlineEntry): boolean {
const showCodeCells = this._configurationService.getValue<boolean>(NotebookSetting.outlineShowCodeCells);
const showCodeCellSymbols = this._configurationService.getValue<boolean>(NotebookSetting.outlineShowCodeCellSymbols);
const showMarkdownHeadersOnly = this._configurationService.getValue<boolean>(NotebookSetting.outlineShowMarkdownHeadersOnly);

// if any are true, return true, this entry should NOT be included in the outline
if (
(showMarkdownHeadersOnly && entry.cell.cellKind === CellKind.Markup && entry.level === NotebookOutlineConstants.NonHeaderOutlineLevel) || // show headers only + cell is mkdn + is level 7 (not header)
(!showCodeCells && entry.cell.cellKind === CellKind.Code) || // show code cells off + cell is code
(!showCodeCellSymbols && entry.cell.cellKind === CellKind.Code && entry.level > NotebookOutlineConstants.NonHeaderOutlineLevel) // show symbols off + cell is code + is level >7 (nb symbol levels)
) {
return true;
}

return false;
}

*getChildren(element: NotebookCellOutline | OutlineEntry): Iterable<OutlineEntry> {
const showCodeCells = this._configurationService.getValue<boolean>(NotebookSetting.outlineShowCodeCells);
const showCodeCellSymbols = this._configurationService.getValue<boolean>(NotebookSetting.outlineShowCodeCellSymbols);
const showMarkdownHeadersOnly = this._configurationService.getValue<boolean>(NotebookSetting.outlineShowMarkdownHeadersOnly);

const isOutline = element instanceof NotebookCellOutline;
const entries = isOutline ? this._getEntries() : element.children;
const entries = isOutline ? this.outlineDataSourceRef?.object?.entries ?? [] : element.children;

for (const entry of entries) {
if (entry.cell.cellKind === CellKind.Markup) {
Expand All @@ -367,14 +414,14 @@ export class NotebookOutlinePaneProvider implements IDataSource<NotebookCellOutl

export class NotebookBreadcrumbsProvider implements IBreadcrumbsDataSource<OutlineEntry> {
constructor(
private _getActiveElement: () => OutlineEntry | undefined,
private readonly outlineDataSourceRef: IReference<INotebookCellOutlineDataSource> | undefined,
@IConfigurationService private readonly _configurationService: IConfigurationService,
) { }

getBreadcrumbElements(): readonly OutlineEntry[] {
const result: OutlineEntry[] = [];
const showCodeCells = this._configurationService.getValue<boolean>(NotebookSetting.breadcrumbsShowCodeCells);
let candidate = this._getActiveElement();
let candidate = this.outlineDataSourceRef?.object?.activeElement;
while (candidate) {
if (showCodeCells || candidate.cell.cellKind !== CellKind.Code) {
result.unshift(candidate);
Expand All @@ -401,15 +448,14 @@ class NotebookComparator implements IOutlineComparator<OutlineEntry> {
}

export class NotebookCellOutline implements IOutline<OutlineEntry> {

private readonly _dispoables = new DisposableStore();
private readonly _disposables = new DisposableStore();

private readonly _onDidChange = new Emitter<OutlineChangeEvent>();

readonly onDidChange: Event<OutlineChangeEvent> = this._onDidChange.event;

get entries(): OutlineEntry[] {
return this._outlineProviderReference?.object?.entries ?? [];
return this._outlineDataSourceReference?.object?.entries ?? [];
}

private readonly _entriesDisposables = new DisposableStore();
Expand All @@ -419,46 +465,51 @@ export class NotebookCellOutline implements IOutline<OutlineEntry> {
readonly outlineKind = 'notebookCells';

get activeElement(): OutlineEntry | undefined {
return this._outlineProviderReference?.object?.activeElement;
if (this._target === OutlineTarget.OutlinePane) {
return (this.config.treeDataSource as NotebookOutlinePaneProvider).getActiveEntry();
} else {
console.error('activeElement should not be called outside of the OutlinePane');
return undefined;
}
}

private _outlineProviderReference: IReference<NotebookCellOutlineProvider> | undefined;
private _outlineDataSourceReference: IReference<NotebookCellOutlineDataSource> | undefined;
private readonly _localDisposables = new DisposableStore();

constructor(
private readonly _editor: INotebookEditorPane,
_target: OutlineTarget,
private readonly _target: OutlineTarget,
@IInstantiationService instantiationService: IInstantiationService,
@IEditorService private readonly _editorService: IEditorService,
@IConfigurationService _configurationService: IConfigurationService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
) {
const installSelectionListener = () => {
const notebookEditor = _editor.getControl();
if (!notebookEditor?.hasModel()) {
this._outlineProviderReference?.dispose();
this._outlineProviderReference = undefined;
this._outlineDataSourceReference?.dispose();
this._outlineDataSourceReference = undefined;
this._localDisposables.clear();
} else {
this._outlineProviderReference?.dispose();
this._outlineDataSourceReference?.dispose();
this._localDisposables.clear();
this._outlineProviderReference = instantiationService.invokeFunction((accessor) => accessor.get(INotebookCellOutlineProviderFactory).getOrCreate(notebookEditor, _target));
this._localDisposables.add(this._outlineProviderReference.object.onDidChange(e => {
this._outlineDataSourceReference = instantiationService.invokeFunction((accessor) => accessor.get(INotebookCellOutlineDataSourceFactory).getOrCreate(notebookEditor));
this._localDisposables.add(this._outlineDataSourceReference.object.onDidChange(e => {
Yoyokrazy marked this conversation as resolved.
Show resolved Hide resolved
this._onDidChange.fire(e);
}));
}
};

this._dispoables.add(_editor.onDidChangeModel(() => {
this._disposables.add(_editor.onDidChangeModel(() => {
installSelectionListener();
}));

installSelectionListener();
const delegate = new NotebookOutlineVirtualDelegate();
const renderers = [instantiationService.createInstance(NotebookOutlineRenderer, this._editor.getControl(), _target)];
const renderers = [instantiationService.createInstance(NotebookOutlineRenderer, this._editor.getControl(), this._target)];
const comparator = new NotebookComparator();

const options: IWorkbenchDataTreeOptions<OutlineEntry, FuzzyScore> = {
collapseByDefault: _target === OutlineTarget.Breadcrumbs || (_target === OutlineTarget.OutlinePane && _configurationService.getValue(OutlineConfigKeys.collapseItems) === OutlineConfigCollapseItemsValues.Collapsed),
collapseByDefault: this._target === OutlineTarget.Breadcrumbs || (this._target === OutlineTarget.OutlinePane && _configurationService.getValue(OutlineConfigKeys.collapseItems) === OutlineConfigCollapseItemsValues.Collapsed),
expandOnlyOnTwistieClick: true,
multipleSelectionSupport: false,
accessibilityProvider: new NotebookOutlineAccessibility(),
Expand All @@ -467,25 +518,33 @@ export class NotebookCellOutline implements IOutline<OutlineEntry> {
};

this.config = {
treeDataSource: instantiationService.createInstance(NotebookOutlinePaneProvider, () => (this.entries ?? [])),
quickPickDataSource: instantiationService.createInstance(NotebookQuickPickProvider, () => (this.entries ?? [])),
breadcrumbsDataSource: instantiationService.createInstance(NotebookBreadcrumbsProvider, () => (this.activeElement)),
treeDataSource: instantiationService.createInstance(NotebookOutlinePaneProvider, this._outlineDataSourceReference),
quickPickDataSource: instantiationService.createInstance(NotebookQuickPickProvider, this._outlineDataSourceReference),
breadcrumbsDataSource: instantiationService.createInstance(NotebookBreadcrumbsProvider, this._outlineDataSourceReference),
delegate,
renderers,
comparator,
options
};
}

async setFullSymbols(cancelToken: CancellationToken) {
await this._outlineProviderReference?.object?.setFullSymbols(cancelToken);
// TODO@Yoyokrazy -- transfer this to the NotebookCellOutlineDataSource and call from there. minimal recompute
async initialize(cancelToken: CancellationToken) {
const showAllGotoSymbols = this._configurationService.getValue<boolean>(NotebookSetting.gotoSymbolsAllSymbols);
const showAllOutlineSymbols = this._configurationService.getValue<boolean>(NotebookSetting.outlineShowCodeCellSymbols);
if (this._target === OutlineTarget.QuickPick && showAllGotoSymbols) {
await this._outlineDataSourceReference?.object?.setFullSymbols(cancelToken);
} else if (this._target === OutlineTarget.OutlinePane && showAllOutlineSymbols) {
// No need to wait for this, we want the outline to show up quickly.
void this._outlineDataSourceReference?.object?.setFullSymbols(cancelToken);
}
}

get uri(): URI | undefined {
return this._outlineProviderReference?.object?.uri;
return this._outlineDataSourceReference?.object?.uri;
}
get isEmpty(): boolean {
return this._outlineProviderReference?.object?.isEmpty ?? true;
return this._outlineDataSourceReference?.object?.isEmpty ?? true;
}
async reveal(entry: OutlineEntry, options: IEditorOptions, sideBySide: boolean): Promise<void> {
await this._editorService.openEditor({
Expand Down Expand Up @@ -562,9 +621,9 @@ export class NotebookCellOutline implements IOutline<OutlineEntry> {

dispose(): void {
this._onDidChange.dispose();
this._dispoables.dispose();
this._disposables.dispose();
this._entriesDisposables.dispose();
this._outlineProviderReference?.dispose();
this._outlineDataSourceReference?.dispose();
this._localDisposables.dispose();
}
}
Expand All @@ -576,7 +635,6 @@ export class NotebookOutlineCreator implements IOutlineCreator<NotebookEditor, O
constructor(
@IOutlineService outlineService: IOutlineService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
) {
const reg = outlineService.registerOutlineCreator(this);
this.dispose = () => reg.dispose();
Expand All @@ -588,16 +646,7 @@ export class NotebookOutlineCreator implements IOutlineCreator<NotebookEditor, O

async createOutline(editor: NotebookEditor, target: OutlineTarget, cancelToken: CancellationToken): Promise<IOutline<OutlineEntry> | undefined> {
const outline = this._instantiationService.createInstance(NotebookCellOutline, editor, target);

const showAllGotoSymbols = this._configurationService.getValue<boolean>(NotebookSetting.gotoSymbolsAllSymbols);
const showAllOutlineSymbols = this._configurationService.getValue<boolean>(NotebookSetting.outlineShowCodeCellSymbols);
if (target === OutlineTarget.QuickPick && showAllGotoSymbols) {
await outline.setFullSymbols(cancelToken);
} else if (target === OutlineTarget.OutlinePane && showAllOutlineSymbols) {
// No need to wait for this, we want the outline to show up quickly.
void outline.setFullSymbols(cancelToken);
}

await outline.initialize(cancelToken);
return outline;
}
}
Expand Down Expand Up @@ -677,7 +726,6 @@ registerAction2(class ToggleShowMarkdownHeadersOnly extends Action2 {
}
});


registerAction2(class ToggleCodeCellEntries extends Action2 {
constructor() {
super({
Expand Down
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 @@ -757,7 +757,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