src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts/DocumentSymbolsOutline/_createOutline
class DocumentSymbolsOutline:	private async _createOutline(contentChangeEvent?: IModelContentChangedEvent): Promise<void> {

		this._outlineDisposables.clear();
		if (!contentChangeEvent) {
			this._setOutlineModel(undefined);
		}

		if (!this._editor.hasModel()) {
			return;
		}
		const buffer = this._editor.getModel();
		if (!DocumentSymbolProviderRegistry.has(buffer)) {
			return;
		}

		const cts = new CancellationTokenSource();
		const versionIdThen = buffer.getVersionId();
		const timeoutTimer = new TimeoutTimer();

		this._outlineDisposables.add(timeoutTimer);
		this._outlineDisposables.add(toDisposable(() => cts.dispose(true)));

		try {
			let model = await OutlineModel.create(buffer, cts.token);
			if (cts.token.isCancellationRequested) {
				// cancelled -> do nothing
				return;
			}

			if (TreeElement.empty(model) || !this._editor.hasModel()) {
				// empty -> no outline elements
				this._setOutlineModel(model);
				return;
			}

			// heuristic: when the symbols-to-lines ratio changes by 50% between edits
			// wait a little (and hope that the next change isn't as drastic).
			if (contentChangeEvent && this._outlineModel && buffer.getLineCount() >= 25) {
				const newSize = TreeElement.size(model);
				const newLength = buffer.getValueLength();
				const newRatio = newSize / newLength;
				const oldSize = TreeElement.size(this._outlineModel);
				const oldLength = newLength - contentChangeEvent.changes.reduce((prev, value) => prev + value.rangeLength, 0);
				const oldRatio = oldSize / oldLength;
				if (newRatio <= oldRatio * 0.5 || newRatio >= oldRatio * 1.5) {
					// wait for a better state and ignore current model when more
					// typing has happened
					const value = await raceCancellation(timeout(2000).then(() => true), cts.token, false);
					if (!value) {
						return;
					}
				}
			}

			// copy the model
			model = model.adopt();

			// feature: show markers with outline element
			this._applyMarkersToOutline(model);
			this._outlineDisposables.add(this._markerDecorationsService.onDidChangeMarker(textModel => {
				if (isEqual(model.uri, textModel.uri)) {
					this._applyMarkersToOutline(model);
					this._onDidChange.fire({});
				}
			}));
			this._outlineDisposables.add(this._configurationService.onDidChangeConfiguration(e => {
				if (e.affectsConfiguration(OutlineConfigKeys.problemsEnabled)) {
					if (this._configurationService.getValue(OutlineConfigKeys.problemsEnabled)) {
						this._applyMarkersToOutline(model);
					} else {
						model.updateMarker([]);
					}
					this._onDidChange.fire({});
				}
				if (e.affectsConfiguration('outline')) {
					// outline filtering, problems on/off
					this._onDidChange.fire({});
				}
				if (e.affectsConfiguration('breadcrumbs') && this._editor.hasModel()) {
					// breadcrumbs filtering
					this._breadcrumbsDataSource.update(model, this._editor.getPosition());
					this._onDidChange.fire({});
				}
			}));

			// feature: toggle icons
			this._outlineDisposables.add(this._configurationService.onDidChangeConfiguration(e => {
				if (e.affectsConfiguration(OutlineConfigKeys.icons)) {
					this._onDidChange.fire({});
				}
				if (e.affectsConfiguration('outline')) {
					this._onDidChange.fire({});
				}
			}));

			// feature: update active when cursor changes
			this._outlineDisposables.add(this._editor.onDidChangeCursorPosition(_ => {
				timeoutTimer.cancelAndSet(() => {
					if (!buffer.isDisposed() && versionIdThen === buffer.getVersionId() && this._editor.hasModel()) {
						this._breadcrumbsDataSource.update(model, this._editor.getPosition());
						this._onDidChange.fire({ affectOnlyActiveElement: true });
					}
				}, 150);
			}));

			// update properties, send event
			this._setOutlineModel(model);

		} catch (err) {
			this._setOutlineModel(undefined);
			onUnexpectedError(err);
		}
	}