提交 90d2232f 编写于 作者: J Johannes Rieken

async loading, keep state when editors change

上级 894e3a74
......@@ -12,29 +12,38 @@ import { fuzzyScore } from '../../../../base/common/filters';
import { IPosition } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { values } from 'vs/base/common/map';
import URI from 'vs/base/common/uri';
export function getOutline(model: ITextModel): TPromise<OutlineItemGroup[]> {
let outlines = new Array<OutlineItemGroup>();
let promises = DocumentSymbolProviderRegistry.ordered(model).map((provider, i) => {
export function getOutline(model: ITextModel): TPromise<OutlineItemGroup>[] {
return DocumentSymbolProviderRegistry.ordered(model).map((provider, i) => {
let source = `provider${i}`;
return asWinJsPromise(token => provider.provideDocumentSymbols(model, token)).then(result => {
let source = `provider${i}`;
let items = result.map(info => asOutlineItem(info, undefined));
outlines.push(new OutlineItemGroup(source, items));
let items = result.map(info => asOutlineItem(info, source));
return new OutlineItemGroup(source, items);
}, err => {
//
//todo@joh capture error in group
return new OutlineItemGroup(source, []);
});
});
return TPromise.join(promises).then(() => outlines);
}
function asOutlineItem(info: SymbolInformation, parent: OutlineItem): OutlineItem {
function asOutlineItem(info: SymbolInformation, parentOrExtensionId: OutlineItem | string): OutlineItem {
// complex id-computation which contains the origin/extension,
// the parent path, and some dedupe logic when names collide
let parent: OutlineItem;
let id = info.name;
if (parent) {
id = parent.id + info.name;
for (let i = 1; parent.children.has(id); i++) {
id = parent.id + info.name + i;
if (typeof parentOrExtensionId === 'string') {
id = parentOrExtensionId + id;
} else if (parentOrExtensionId) {
parent = parentOrExtensionId;
id = parentOrExtensionId.id + info.name;
for (let i = 1; parentOrExtensionId.children.has(id); i++) {
id = parentOrExtensionId.id + info.name + i;
}
}
// build item and recurse
let res = new OutlineItem(id, info, parent);
if (info.children) {
for (const child of info.children) {
......@@ -138,3 +147,41 @@ export class OutlineItemGroup {
return undefined;
}
}
export class OutlineModel {
constructor(
readonly uri: URI,
private _requests: TPromise<OutlineItemGroup>[]
) {
//
}
dispose(): void {
this._cancelRequests();
}
private _cancelRequests() {
for (const req of this._requests) {
req.cancel();
}
}
all(): TPromise<OutlineItemGroup>[] {
return this._requests;
}
selected(): TPromise<OutlineItemGroup> {
// todo@joh allow to 'select' results from different providers
return this._requests[0];
}
merge(other: OutlineModel): boolean {
if (this.uri.toString() !== other.uri.toString()) {
return false;
}
this._cancelRequests();
this._requests = other._requests;
return true;
}
}
......@@ -30,9 +30,10 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IViewOptions, ViewsViewletPanel } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { OutlineItem, OutlineItemGroup, getOutline } from './outlineModel';
import { OutlineItem, getOutline, OutlineModel } from './outlineModel';
import { OutlineDataSource, OutlineItemComparator, OutlineItemCompareType, OutlineItemFilter, OutlineRenderer, OutlineTreeState } from './outlineTree';
import { KeyCode } from '../../../../base/common/keyCodes';
import { LRUCache } from '../../../../base/common/map';
class ActiveEditorOracle {
......@@ -93,7 +94,8 @@ export class OutlinePanel extends ViewsViewletPanel {
private _tree: Tree;
private _treeFilter: OutlineItemFilter;
private _treeComparator: OutlineItemComparator;
private _followCursor: boolean = true;
private _treeStates = new LRUCache<string, OutlineTreeState>(10);
private _followCursor = true;
constructor(
options: IViewOptions,
......@@ -173,94 +175,96 @@ export class OutlinePanel extends ViewsViewletPanel {
}
}
private _onEditor(editor: ICodeEditor): void {
private async _onEditor(editor: ICodeEditor): TPromise<void> {
dispose(this._editorDisposables);
this._editorDisposables = new Array();
this._input.disable();
if (!editor) {
//
return;
// todo@joh show empty message
return undefined;
}
// todo@joh show pending...
const promise = getOutline(editor.getModel()).then(outline => {
let model = <OutlineItemGroup>this._tree.getInput();
let [first] = outline;
if (!first) {
return; // todo@joh
let textModel = editor.getModel();
let oldModel = <OutlineModel>this._tree.getInput();
let model = new OutlineModel(textModel.uri, getOutline(textModel));
if (oldModel && oldModel.merge(model)) {
model = oldModel;
this._tree.refresh(undefined, true);
} else {
// persist state
if (oldModel) {
let state = OutlineTreeState.capture(this._tree);
this._treeStates.set(oldModel.uri.toString(), state);
}
await this._tree.setInput(model);
let state = this._treeStates.get(model.uri.toString());
OutlineTreeState.restore(this._tree, state);
}
if (model instanceof OutlineItemGroup && first.source === model.source) {
model.children.splice(0, model.children.length, ...first.children);
this._tree.refresh(undefined, true);
} else {
this._tree.setInput(first);
// wait for the actual model to work with...
let itemGroup = await model.selected();
this._input.enable();
// feature: filter on type
// on type -> update filters
// on first type -> capture tree state
// on erase -> restore captured tree state
let beforePatternState: OutlineTreeState;
this._editorDisposables.push(this._input.onDidChange(async pattern => {
if (!beforePatternState) {
beforePatternState = OutlineTreeState.capture(this._tree);
}
let item = itemGroup.updateMatches(pattern);
await this._tree.refresh(undefined, true);
if (item) {
await this._tree.reveal(item);
this._tree.setFocus(item, this);
this._tree.setSelection([item], this);
this._tree.expandAll(undefined /*all*/);
}
this._editorDisposables.push(editor.onDidChangeCursorSelection(async e => {
if (!this._followCursor || e.reason !== CursorChangeReason.Explicit) {
return;
}
let item = model.getItemEnclosingPosition({
lineNumber: e.selection.selectionStartLineNumber,
column: e.selection.selectionStartColumn
});
if (item) {
await this._tree.reveal(item);
this._tree.setFocus(item, this);
this._tree.setSelection([item], this);
} else {
this._tree.setSelection([], this);
}
}));
this._input.enable();
let beforePatternState: OutlineTreeState;
this._editorDisposables.push(this._input.onDidChange(async pattern => {
if (!beforePatternState) {
beforePatternState = OutlineTreeState.capture(this._tree);
}
let item = model.updateMatches(pattern);
await this._tree.refresh(undefined, true);
if (item) {
await this._tree.reveal(item);
this._tree.setFocus(item, this);
this._tree.setSelection([item], this);
this._tree.expandAll(undefined /*all*/);
}
if (!pattern && beforePatternState) {
await OutlineTreeState.restore(this._tree, beforePatternState);
beforePatternState = undefined;
}
}));
this._editorDisposables.push(this._tree.onDidChangeSelection(e => {
if (e.payload === this) {
return;
}
let [first] = e.selection;
if (first instanceof OutlineItem) {
let { range } = first.symbol.location;
editor.revealRangeInCenterIfOutsideViewport(range, ScrollType.Smooth);
editor.setSelection(Range.collapseToStart(range));
// editor.focus();
}
}));
}, err => {
//todo@joh have an error screen
console.error(err);
});
if (!pattern && beforePatternState) {
await OutlineTreeState.restore(this._tree, beforePatternState);
beforePatternState = undefined;
}
}));
this._editorDisposables.push({
dispose() {
promise.cancel();
// feature: reveal outline selection in editor
// on change -> reveal/select defining range
this._editorDisposables.push(this._tree.onDidChangeSelection(e => {
if (e.payload === this) {
return;
}
});
let [first] = e.selection;
if (first instanceof OutlineItem) {
let { range } = first.symbol.location;
editor.revealRangeInCenterIfOutsideViewport(range, ScrollType.Smooth);
editor.setSelection(Range.collapseToStart(range));
// editor.focus();
}
}));
// feature: reveal editor selection in outline
this._editorDisposables.push(editor.onDidChangeCursorSelection(async e => {
if (!this._followCursor || e.reason !== CursorChangeReason.Explicit) {
return;
}
let item = itemGroup.getItemEnclosingPosition({
lineNumber: e.selection.selectionStartLineNumber,
column: e.selection.selectionStartColumn
});
if (item) {
await this._tree.reveal(item);
this._tree.setFocus(item, this);
this._tree.setSelection([item], this);
} else {
this._tree.setSelection([], this);
}
}));
}
}
......@@ -10,7 +10,7 @@ import * as dom from 'vs/base/browser/dom';
import { symbolKindToCssClass } from 'vs/editor/common/modes';
import { Range } from 'vs/editor/common/core/range';
import { IDataSource, IRenderer, ITree, ISorter, IFilter } from 'vs/base/parts/tree/browser/tree';
import { OutlineItemGroup, OutlineItem } from './outlineModel';
import { OutlineItem, OutlineModel } from './outlineModel';
import { HighlightedLabel } from '../../../../base/browser/ui/highlightedlabel/highlightedLabel';
import { createMatches } from '../../../../base/common/filters';
import { values } from 'vs/base/common/map';
......@@ -56,27 +56,29 @@ export class OutlineDataSource implements IDataSource {
}
}
hasChildren(tree: ITree, element: OutlineItemGroup | OutlineItem): boolean {
if (element instanceof OutlineItemGroup) {
return element.children.length > 0;
hasChildren(tree: ITree, element: OutlineModel | OutlineItem): boolean {
if (element instanceof OutlineModel) {
return element.all().length > 0;
} else {
let res = element.children.size > 0;
if (res) {
if (element.children.size === 0) {
return false;
} else {
let res: boolean;
element.children.forEach(child => res = res || Boolean(child.matches));
return res;
}
return res;
}
}
async getChildren(tree: ITree, element: OutlineItemGroup | OutlineItem): TPromise<any, any> {
if (element instanceof OutlineItemGroup) {
return element.children;
async getChildren(tree: ITree, element: OutlineModel | OutlineItem): TPromise<OutlineItem[]> {
if (element instanceof OutlineModel) {
return (await element.selected()).children;
} else {
return values(element.children);
}
}
async getParent(tree: ITree, element: OutlineItemGroup | OutlineItem): TPromise<any, any> {
async getParent(tree: ITree, element: OutlineItem | any): TPromise<OutlineItem> {
return element instanceof OutlineItem ? element.parent : undefined;
}
......@@ -137,13 +139,14 @@ export class OutlineTreeState {
}
static async restore(tree: ITree, state: OutlineTreeState): TPromise<void> {
let input = <OutlineItemGroup>tree.getInput();
if (!(input instanceof OutlineItemGroup)) {
let model = <OutlineModel>tree.getInput();
if (!state || !(model instanceof OutlineModel)) {
return TPromise.as(undefined);
}
let group = await model.selected();
let items: OutlineItem[] = [];
for (const id of state.expanded) {
let item = input.getItemById(id);
let item = group.getItemById(id);
if (item) {
items.push(item);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册