diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index df14bde2406..4dcbe111f95 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -15,6 +15,6 @@ export interface IDelegate { export interface IRenderer { templateId: string; renderTemplate(container: HTMLElement): TTemplateData; - renderElement(element: TElement, templateData: TTemplateData): void; + renderElement(element: TElement, index: number, templateData: TTemplateData): void; disposeTemplate(templateData: TTemplateData): void; } diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 6cc5c7543b7..019603d1164 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -135,6 +135,10 @@ export class ListView implements IScrollable { return this.items.length; } + element(index: number): T { + return this.items[index].element; + } + layout(height?: number): void { this.setRenderHeight(height || DOM.getContentHeight(this.domNode)); this.setScrollTop(this.renderTop); @@ -212,7 +216,7 @@ export class ListView implements IScrollable { const renderer = this.renderers[item.templateId]; item.row.domNode.style.top = `${ this.rangeMap.positionAt(index) }px`; item.row.domNode.style.height = `${ item.size }px`; - renderer.renderElement(item.element, item.row.templateData); + renderer.renderElement(item.element, index, item.row.templateData); } private removeItemFromDOM(item: IItem): void { diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index a127624d4e4..8e3fc109c40 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -6,7 +6,7 @@ import 'vs/css!./list'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { toggleClass } from 'vs/base/browser/dom'; -import { IDelegate, IIdentityProvider, IRenderer } from './list'; +import { IDelegate, IRenderer } from './list'; import { ListView } from './listView'; interface ITraitTemplateData { @@ -19,7 +19,7 @@ class TraitRenderer implements IRenderer> private elements: { [id: string]: T }; constructor( - private controller: TraitController, + private controller: TraitController, private renderer: IRenderer ) {} @@ -32,9 +32,9 @@ class TraitRenderer implements IRenderer> return { container, data }; } - renderElement(element: T, templateData: ITraitTemplateData): void { - toggleClass(templateData.container, this.controller.trait, this.controller.contains(element)); - this.renderer.renderElement(element, templateData.data); + renderElement(element: T, index: number, templateData: ITraitTemplateData): void { + toggleClass(templateData.container, this.controller.trait, this.controller.contains(index)); + this.renderer.renderElement(element, index, templateData.data); } disposeTemplate(templateData: ITraitTemplateData): void { @@ -42,42 +42,57 @@ class TraitRenderer implements IRenderer> } } -class TraitController { +class TraitController { - private elements: { [id: string]: T }; + private indexes: number[]; - constructor(private _trait: string, private identityProvider: IIdentityProvider) { - this.set(); + constructor(private _trait: string) { + this.indexes = []; + } + + splice(start: number, deleteCount: number, insertCount: number): void { + const diff = insertCount - deleteCount; + const end = start + deleteCount; + const indexes = []; + + for (const index of indexes) { + if (index >= start && index < end) { + continue; + } + + indexes.push(index > start ? index + diff : index); + } + + this.indexes = indexes; } get trait(): string { return this._trait; } - set(...elements: T[]): void { - this.elements = Object.create(null); - this.add(...elements); + set(indexes: number[]): number[] { + const result = this.indexes; + this.indexes = indexes; + return result; } - add(...elements: T[]): void { - for (const element of elements) { - const id = this.identityProvider.getId(element); - this.elements[id] = element; + add(index: number): void { + if (this.contains(index)) { + return; } + + this.indexes.push(index); } - remove(...elements: T[]): void { - for (const element of elements) { - const id = this.identityProvider.getId(element); - delete this.elements[id]; - } + remove(index: number): void { + this.indexes = this.indexes.filter(i => i === index); } - contains(element: T): boolean { - return !!this.elements[this.identityProvider.getId(element)]; + contains(index: number): boolean { + return this.indexes.some(i => i === index); } - wrapRenderer(renderer: IRenderer): IRenderer> { + wrapRenderer(renderer: IRenderer): IRenderer> { return new TraitRenderer(this, renderer); } } @@ -85,17 +100,16 @@ class TraitController { export class List implements IDisposable { private view: ListView; - private focus: TraitController; - private selection: TraitController; + private focus: TraitController; + private selection: TraitController; constructor( container: HTMLElement, delegate: IDelegate, - renderers: IRenderer[], - private identityProvider: IIdentityProvider + renderers: IRenderer[] ) { - this.focus = new TraitController('focused', identityProvider); - this.selection = new TraitController('selected', identityProvider); + this.focus = new TraitController('focused'); + this.selection = new TraitController('selected'); renderers = renderers.map(r => { r = this.focus.wrapRenderer(r); @@ -107,9 +121,9 @@ export class List implements IDisposable { } splice(start: number, deleteCount: number, ...elements: T[]): void { - const deleted = this.view.splice(start, deleteCount, ...elements); - this.focus.remove(...deleted); - this.selection.remove(...deleted); + this.focus.splice(start, deleteCount, elements.length); + this.selection.splice(start, deleteCount, elements.length); + this.view.splice(start, deleteCount, ...elements); } get length(): number { @@ -120,12 +134,14 @@ export class List implements IDisposable { this.view.layout(height); } - setSelection(...elements: T[]): void { - this.selection.set(...elements); + setSelection(...indexes: number[]): void { + indexes = indexes.concat(this.selection.set(indexes)); + indexes.forEach(i => this.view.splice(i, 1, this.view.element(i))); } - setFocus(...elements: T[]): void { - this.focus.set(...elements); + setFocus(...indexes: number[]): void { + indexes = indexes.concat(this.focus.set(indexes)); + indexes.forEach(i => this.view.splice(i, 1, this.view.element(i))); } dispose(): void { diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts index 6db29ce0a38..70bbe34b6ff 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts @@ -297,7 +297,7 @@ class Renderer implements IRenderer { return data; } - renderElement(element: CompletionItem, templateData: ISuggestionTemplateData): void { + renderElement(element: CompletionItem, index: number, templateData: ISuggestionTemplateData): void { const data = templateData; const suggestion = (element).suggestion; @@ -517,7 +517,7 @@ export class SuggestWidget implements EditorBrowser.IContentWidget, IDisposable this.listElement = append(this.element, $('.tree')); this.details = new SuggestionDetails(this.element, this); - const identityProvider = new IdentityProvider(); + // const identityProvider = new IdentityProvider(); let renderer: IRenderer = instantiationService.createInstance(Renderer, this); @@ -536,7 +536,7 @@ export class SuggestWidget implements EditorBrowser.IContentWidget, IDisposable // }; // this.list = new TreeImpl.Tree(this.listElement, configuration, options); - this.list = new List(this.listElement, new Delegate(), [renderer], identityProvider); + this.list = new List(this.listElement, new Delegate(), [renderer]); this.toDispose = [ editor.addListener2(EditorCommon.EventType.ModelChanged, () => this.onModelModeChanged()), @@ -735,7 +735,9 @@ export class SuggestWidget implements EditorBrowser.IContentWidget, IDisposable visibleCount = this.completionModel.items.length; } - if (visibleCount === 0) { + const isEmpty = visibleCount === 0; + + if (isEmpty) { if (e.auto) { this.setState(State.Hidden); } else { @@ -746,63 +748,40 @@ export class SuggestWidget implements EditorBrowser.IContentWidget, IDisposable } } - if (this.telemetryTimer) { - this.telemetryTimer.data = { reason: 'empty' }; - this.telemetryTimer.stop(); - this.telemetryTimer = null; - } - this.completionModel = null; - return; + + } else { + const currentWord = e.currentWord; + const currentWordLowerCase = currentWord.toLowerCase(); + let bestSuggestionIndex = -1; + let bestScore = -1; + + this.completionModel.items.forEach((item, index) => { + const score = computeScore(item.suggestion.label, currentWord, currentWordLowerCase); + + if (score > bestScore) { + bestScore = score; + bestSuggestionIndex = index; + } + }); + + this.telemetryData = this.telemetryData || {}; + this.telemetryData.suggestionCount = this.completionModel.items.length; + this.telemetryData.suggestedIndex = bestSuggestionIndex; + this.telemetryData.hintLength = currentWord.length; + + this.list.splice(0, this.list.length, ...this.completionModel.items); + + this.list.setFocus(bestSuggestionIndex); + // console.log(bestSuggestion.suggestion.label) + this.setState(State.Open); } - const first = this.completionModel.items[0]; - if (first) { - this.list.setFocus(first); + if (this.telemetryTimer) { + this.telemetryTimer.data = { reason: isEmpty ? 'empty' : 'results' }; + this.telemetryTimer.stop(); + this.telemetryTimer = null; } - - const second = this.completionModel.items[1]; - if (second) { - this.list.setSelection(second); - } - - this.list.splice(0, this.list.length, ...this.completionModel.items); - - // const navigator = this.list.getNavigator(); - // const currentWord = e.currentWord; - // const currentWordLowerCase = currentWord.toLowerCase(); - // const suggestions = model.items; - - // let index = 0; - // let bestSuggestionIndex = -1; - // let bestSuggestion = suggestions[0]; - // let bestScore = -1; - // let item: CompletionItem; - - // while (item = navigator.next()) { - // const score = computeScore(item.suggestion.label, currentWord, currentWordLowerCase); - - // if (score > bestScore) { - // bestScore = score; - // bestSuggestion = item; - // bestSuggestionIndex = index; - // } - // } - - // this.list.setFocus(bestSuggestion, { firstSuggestion: true }); - - // this.telemetryData = this.telemetryData || {}; - // this.telemetryData.suggestionCount = suggestions.length; - // this.telemetryData.suggestedIndex = bestSuggestionIndex; - // this.telemetryData.hintLength = currentWord.length; - - this.setState(State.Open); - - // if (this.telemetryTimer) { - // this.telemetryTimer.data = { reason: 'results' }; - // this.telemetryTimer.stop(); - // this.telemetryTimer = null; - // } } private onDidCancel(e: ICancelEvent) {