src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts/NotebookCellOutline/_recomputeState
class NotebookCellOutline:	private _recomputeState(): void {
		this._entriesDisposables.clear();
		this._activeEntry = undefined;
		this._entries.length = 0;

		const { viewModel } = this._editor;
		if (!viewModel) {
			return;
		}

		let includeCodeCells = true;
		if (this._target === OutlineTarget.OutlinePane) {
			includeCodeCells = this._configurationService.getValue<boolean>('notebook.outline.showCodeCells');
		} else if (this._target === OutlineTarget.Breadcrumbs) {
			includeCodeCells = this._configurationService.getValue<boolean>('notebook.breadcrumbs.showCodeCells');
		}

		const focusedCellIndex = viewModel.getFocus().start;
		const focused = viewModel.cellAt(focusedCellIndex)?.handle;
		const entries: OutlineEntry[] = [];

		for (let i = 0; i < viewModel.length; i++) {
			const cell = viewModel.viewCells[i];
			const isMarkdown = cell.cellKind === CellKind.Markup;
			if (!isMarkdown && !includeCodeCells) {
				continue;
			}

			// The cap the amount of characters that we look at and use the following logic
			// - for MD prefer headings (each header is an entry)
			// - otherwise use the first none-empty line of the cell (MD or code)
			let content = cell.getText().substr(0, 10_000);
			let hasHeader = false;

			if (isMarkdown) {
				for (const token of marked.lexer(content, { gfm: true })) {
					if (token.type === 'heading') {
						hasHeader = true;
						entries.push(new OutlineEntry(entries.length, token.depth, cell, renderMarkdownAsPlaintext({ value: token.text }).trim(), Codicon.markdown));
					}
				}
				if (!hasHeader) {
					content = renderMarkdownAsPlaintext({ value: content });
				}
			}

			if (!hasHeader) {
				const lineMatch = content.match(/^.*\w+.*\w*$/m);
				let preview: string;
				if (!lineMatch) {
					preview = localize('empty', "empty cell");
				} else {
					preview = lineMatch[0].trim();
					if (preview.length >= 64) {
						preview = preview.slice(0, 64) + '…';
					}
				}

				entries.push(new OutlineEntry(entries.length, 7, cell, preview, isMarkdown ? Codicon.markdown : Codicon.code));
			}

			if (cell.handle === focused) {
				this._activeEntry = entries[entries.length - 1];
			}

			// 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
		if (entries.length > 0) {
			let result: OutlineEntry[] = [entries[0]];
			let parentStack: OutlineEntry[] = [entries[0]];

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

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

					} else {
						let 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._entriesDisposables.add(markerServiceListener);
		const updateMarkerUpdater = () => {
			const doUpdateMarker = (clear: boolean) => {
				for (let entry of this._entries) {
					if (clear) {
						entry.clearMarkers();
					} else {
						entry.updateMarkers(this._markerService);
					}
				}
			};
			if (this._configurationService.getValue(OutlineConfigKeys.problemsEnabled)) {
				markerServiceListener.value = this._markerService.onMarkerChanged(e => {
					if (e.some(uri => viewModel.viewCells.some(cell => isEqual(cell.uri, uri)))) {
						doUpdateMarker(false);
						this._onDidChange.fire({});
					}
				});
				doUpdateMarker(false);
			} else {
				markerServiceListener.clear();
				doUpdateMarker(true);
			}
		};
		updateMarkerUpdater();
		this._entriesDisposables.add(this._configurationService.onDidChangeConfiguration(e => {
			if (e.affectsConfiguration(OutlineConfigKeys.problemsEnabled)) {
				updateMarkerUpdater();
				this._onDidChange.fire({});
			}
		}));

		this._onDidChange.fire({});
	}