in src/vs/workbench/contrib/outline/browser/outlinePanel.ts [444:605]
private async _doUpdate(editor: ICodeEditor | undefined, event: IModelContentChangedEvent | undefined): Promise<void> {
this._editorDisposables.clear();
this._progressBar.infinite().show(150);
const oldModel = this._tree.getInput();
// persist state
if (oldModel) {
this._treeStates.set(oldModel.textModel.uri.toString(), this._tree.getViewState());
}
if (!editor || !editor.hasModel() || !DocumentSymbolProviderRegistry.has(editor.getModel())) {
return this._showMessage(localize('no-editor', "The active editor cannot provide outline information."));
}
let textModel = editor.getModel();
let loadingMessage: IDisposable | undefined;
if (!oldModel) {
loadingMessage = new TimeoutTimer(
() => this._showMessage(localize('loading', "Loading document symbols for '{0}'...", basename(textModel.uri))),
100
);
}
let createdModel = await OutlinePanel._createOutlineModel(textModel, this._editorDisposables);
dispose(loadingMessage);
if (!createdModel) {
return;
}
let newModel = createdModel;
if (TreeElement.empty(newModel)) {
return this._showMessage(localize('no-symbols', "No symbols found in document '{0}'", basename(textModel.uri)));
}
dom.removeClass(this._domNode, 'message');
if (event && oldModel && textModel.getLineCount() >= 25) {
// 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).
let newSize = TreeElement.size(newModel);
let newLength = textModel.getValueLength();
let newRatio = newSize / newLength;
let oldSize = TreeElement.size(oldModel);
let oldLength = newLength - event.changes.reduce((prev, value) => prev + value.rangeLength, 0);
let oldRatio = oldSize / oldLength;
if (newRatio <= oldRatio * 0.5 || newRatio >= oldRatio * 1.5) {
let waitPromise = new Promise<boolean>(resolve => {
let handle: any = setTimeout(() => {
handle = undefined;
resolve(true);
}, 2000);
this._disposables.push({
dispose() {
clearTimeout(handle);
resolve(false);
}
});
});
if (!await waitPromise) {
return;
}
}
}
this._progressBar.stop().hide();
if (oldModel && oldModel.merge(newModel)) {
this._tree.updateChildren();
newModel = oldModel;
} else {
let state = this._treeStates.get(newModel.textModel.uri.toString());
await this._tree.setInput(newModel, state);
}
// transfer focus from domNode to the tree
if (this._domNode === document.activeElement) {
this._tree.domFocus();
}
this._editorDisposables.add(toDisposable(() => this._contextKeyFiltered.reset()));
// feature: reveal outline selection in editor
// on change -> reveal/select defining range
this._editorDisposables.add(this._tree.onDidChangeSelection(e => {
if (e.browserEvent === this._treeFakeUIEvent /* || e.payload && e.payload.didClickOnTwistie */) {
return;
}
let [first] = e.elements;
if (!(first instanceof OutlineElement)) {
return;
}
let focus = false;
let aside = false;
// todo@Joh
if (e.browserEvent) {
if (e.browserEvent.type === 'keydown') {
focus = true;
} else if (e.browserEvent.type === 'click') {
const event = new StandardMouseEvent(e.browserEvent as MouseEvent);
focus = e.browserEvent.detail === 2;
aside = (!this._tree.useAltAsMultipleSelectionModifier && event.altKey)
|| (this._tree.useAltAsMultipleSelectionModifier && (event.ctrlKey || event.metaKey));
}
}
this._revealTreeSelection(newModel, first, focus, aside);
}));
// feature: reveal editor selection in outline
this._revealEditorSelection(newModel, editor.getSelection());
const versionIdThen = newModel.textModel.getVersionId();
this._editorDisposables.add(editor.onDidChangeCursorSelection(e => {
// first check if the document has changed and stop revealing the
// cursor position iff it has -> we will update/recompute the
// outline view then anyways
if (!newModel.textModel.isDisposed() && newModel.textModel.getVersionId() === versionIdThen) {
this._revealEditorSelection(newModel, e.selection);
}
}));
// feature: show markers in outline
const updateMarker = (model: ITextModel, ignoreEmpty?: boolean) => {
if (!this._configurationService.getValue(OutlineConfigKeys.problemsEnabled)) {
return;
}
if (model !== textModel) {
return;
}
const markers: IOutlineMarker[] = [];
for (const [range, marker] of this._markerDecorationService.getLiveMarkers(textModel)) {
if (marker.severity === MarkerSeverity.Error || marker.severity === MarkerSeverity.Warning) {
markers.push({ ...range, severity: marker.severity });
}
}
if (markers.length > 0 || !ignoreEmpty) {
newModel.updateMarker(markers);
this._tree.updateChildren();
}
};
updateMarker(textModel, true);
this._editorDisposables.add(this._markerDecorationService.onDidChangeMarker(updateMarker));
this._editorDisposables.add(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(OutlineConfigKeys.problemsBadges) || e.affectsConfiguration(OutlineConfigKeys.problemsColors)) {
this._tree.updateChildren();
return;
}
if (!e.affectsConfiguration(OutlineConfigKeys.problemsEnabled)) {
return;
}
if (!this._configurationService.getValue(OutlineConfigKeys.problemsEnabled)) {
newModel.updateMarker([]);
this._tree.updateChildren();
} else {
updateMarker(textModel, true);
}
}));
}