提交 5723a2d9 编写于 作者: J Johannes Rieken

simplify completion model, prep work for #3153

上级 2cbef4e3
......@@ -14,137 +14,95 @@ import {ISuggestResult, ISuggestSupport, ISuggestion, ISuggestionFilter} from 'v
import {DefaultFilter, IMatch} from 'vs/editor/common/modes/modesFilters';
import {ISuggestResult2} from '../common/suggest';
function completionItemCompare(item: CompletionItem, otherItem: CompletionItem): number {
const suggestion = item.suggestion;
const otherSuggestion = otherItem.suggestion;
if (typeof suggestion.sortText === 'string' && typeof otherSuggestion.sortText === 'string') {
const one = suggestion.sortText.toLowerCase();
const other = otherSuggestion.sortText.toLowerCase();
if (one < other) {
return -1;
} else if (one > other) {
return 1;
}
}
return suggestion.label.toLowerCase() < otherSuggestion.label.toLowerCase() ? -1 : 1;
}
export class CompletionItem {
suggestion: ISuggestion;
highlights: IMatch[];
support: ISuggestSupport;
container: ISuggestResult;
constructor(public group: CompletionGroup, suggestion: ISuggestion, container: ISuggestResult2) {
this.support = container.support;
private _filter: ISuggestionFilter;
private _support: ISuggestSupport;
constructor(suggestion: ISuggestion, container: ISuggestResult2) {
this._support = container.support;
this.suggestion = suggestion;
this.container = container;
this._filter = container.support && container.support.getFilter() || DefaultFilter;
}
resolveDetails(resource: URI, position: IPosition): TPromise<ISuggestion> {
if (!this.support || typeof this.support.getSuggestionDetails !== 'function') {
if (!this._support || typeof this._support.getSuggestionDetails !== 'function') {
return TPromise.as(this.suggestion);
}
return this.support.getSuggestionDetails(resource, position, this.suggestion);
return this._support.getSuggestionDetails(resource, position, this.suggestion);
}
updateDetails(value: ISuggestion): void {
this.suggestion = assign(this.suggestion, value);
}
}
export class CompletionGroup {
private _items: CompletionItem[];
private cache: CompletionItem[];
private cacheCurrentWord: string;
filter: ISuggestionFilter;
constructor(public model: CompletionModel, raw: ISuggestResult2[]) {
this._items = raw.reduce<CompletionItem[]>((items, result) => {
return items.concat(
result.suggestions
.map(suggestion => new CompletionItem(this, suggestion, result))
);
}, []).sort(completionItemCompare);
this.filter = DefaultFilter;
if (this._items.length > 0) {
const [first] = this._items;
if (first.support) {
this.filter = first.support.getFilter && first.support.getFilter() || this.filter;
}
}
updateHighlights(word: string): boolean {
this.highlights = this._filter(word, this.suggestion);
return !isFalsyOrEmpty(this.highlights);
}
getItems(currentWord: string): CompletionItem[] {
if (currentWord === this.cacheCurrentWord) {
return this.cache;
}
static compare(item: CompletionItem, otherItem: CompletionItem): number {
const suggestion = item.suggestion;
const otherSuggestion = otherItem.suggestion;
let set: CompletionItem[];
if (typeof suggestion.sortText === 'string' && typeof otherSuggestion.sortText === 'string') {
const one = suggestion.sortText.toLowerCase();
const other = otherSuggestion.sortText.toLowerCase();
// try to narrow down when possible, instead of always filtering everything
if (this.cacheCurrentWord && currentWord.substr(0, this.cacheCurrentWord.length) === this.cacheCurrentWord) {
set = this.cache;
} else {
set = this._items;
}
const highlights = set.map(item => this.filter(currentWord, item.suggestion));
const count = highlights.filter(h => !isFalsyOrEmpty(h)).length;
if (count === 0) {
return [];
if (one < other) {
return -1;
} else if (one > other) {
return 1;
}
}
this.cacheCurrentWord = currentWord;
this.cache = set
.map((item, index) => assign(item, { highlights: highlights[index] }))
.filter(item => !isFalsyOrEmpty(item.highlights));
return this.cache;
}
invalidateCache(): void {
this.cacheCurrentWord = null;
return suggestion.label.toLowerCase() < otherSuggestion.label.toLowerCase() ? -1 : 1;
}
}
export class CompletionModel {
private groups: CompletionGroup[];
private cache: CompletionItem[];
private cacheCurrentWord: string;
private _currentWord: string;
private _items: CompletionItem[] = [];
private _filteredItems: CompletionItem[] = undefined;
constructor(public raw: ISuggestResult2[][], public currentWord: string) {
constructor(public raw: ISuggestResult2[], currentWord: string) {
this._currentWord = currentWord;
for (let container of raw) {
for (let suggestion of container.suggestions) {
this._items.push(new CompletionItem(suggestion, container));
}
}
this._items.sort(CompletionItem.compare);
}
this.groups = raw
.filter(s => !!s)
.map(suggestResults => new CompletionGroup(this, suggestResults));
get currentWord(): string {
return this._currentWord;
}
get items(): CompletionItem[] {
if (this.cacheCurrentWord === this.currentWord) {
return this.cache;
set currentWord(value: string) {
if (this._currentWord !== value) {
this._filteredItems = undefined;
this._currentWord = value;
}
}
const result = this.groups.reduce((r, groups) => r.concat(groups.getItems(this.currentWord)), []);
// let's only cache stuff that actually has results
if (result.length > 0) {
this.cache = result;
this.cacheCurrentWord = this.currentWord;
get items(): CompletionItem[] {
if (!this._filteredItems) {
this._filteredItems = [];
for (let item of this._items) {
if (item.updateHighlights(this.currentWord)) {
this._filteredItems.push(item);
}
}
}
return result;
return this._filteredItems;
}
}
\ No newline at end of file
......@@ -171,7 +171,7 @@ export class SuggestModel implements IDisposable {
private requestPromise: TPromise<void>;
private context: Context;
private raw: ISuggestResult2[][];
private raw: ISuggestResult2[];
private completionModel: CompletionModel;
private incomplete: boolean;
......@@ -319,7 +319,7 @@ export class SuggestModel implements IDisposable {
}
this.raw = all;
this.incomplete = all.reduce((r, s) => r || s.reduce((r, s) => r || s.incomplete, false), false);
this.incomplete = all.some(result => result.incomplete);
this.onNewContext(new Context(this.editor, auto));
}).then(null, onUnexpectedError);
......
......@@ -5,6 +5,7 @@
'use strict';
import {sequence} from 'vs/base/common/async';
import {isFalsyOrEmpty} from 'vs/base/common/arrays';
import {illegalArgument, onUnexpectedError} from 'vs/base/common/errors';
import {TPromise} from 'vs/base/common/winjs.base';
import {IModel, IPosition} from 'vs/editor/common/editorCommon';
......@@ -23,37 +24,34 @@ export interface ISuggestResult2 extends ISuggestResult {
support?: ISuggestSupport;
}
export function suggest(model: IModel, position: IPosition, triggerCharacter: string, groups?: ISuggestSupport[][]): TPromise<ISuggestResult2[][]> {
export function suggest(model: IModel, position: IPosition, triggerCharacter: string, groups?: ISuggestSupport[][]): TPromise<ISuggestResult2[]> {
if (!groups) {
groups = SuggestRegistry.orderedGroups(model);
}
const resource = model.getAssociatedResource();
const suggestions: ISuggestResult[][] = [];
const result: ISuggestResult2[] = [];
const factory = groups.map((supports, index) => {
return () => {
// stop as soon as a group produced a result
if (suggestions.length > 0) {
if (result.length > 0) {
return;
}
// for each support in the group ask for suggestions
const promises = supports.map(support => {
return TPromise.join(supports.map(support => {
return support.suggest(resource, position, triggerCharacter).then(values => {
if (!values) {
return;
}
const result: ISuggestResult2[] = [];
for (let suggestResult of values) {
if (!suggestResult
|| !Array.isArray(suggestResult.suggestions)
|| suggestResult.suggestions.length === 0) {
if (!suggestResult || isFalsyOrEmpty(suggestResult.suggestions)) {
continue;
}
......@@ -65,30 +63,16 @@ export function suggest(model: IModel, position: IPosition, triggerCharacter: st
});
}
return result;
}, onUnexpectedError);
});
return TPromise.join(promises).then(values => {
for (let value of values) {
if (Array.isArray(value) && value.length > 0) {
suggestions.push(value);
}
}
});
}));
};
});
return sequence(factory).then(() => {
// add snippets to the first group
const snippets = SnippetsRegistry.getSnippets(model, position);
if (suggestions.length === 0) {
suggestions.push([snippets]);
} else {
suggestions[0].push(snippets);
}
return suggestions;
result.push(snippets);
return result;
});
}
......
......@@ -273,17 +273,15 @@ class ExtHostApiCommands {
position: position && typeConverters.fromPosition(position),
triggerCharacter
};
return this._commands.executeCommand<modes.ISuggestResult[][]>('_executeCompletionItemProvider', args).then(value => {
return this._commands.executeCommand<modes.ISuggestResult[]>('_executeCompletionItemProvider', args).then(value => {
if (value) {
let items: types.CompletionItem[] = [];
let incomplete: boolean;
for (let group of value) {
for (let suggestions of group) {
incomplete = suggestions.incomplete || incomplete;
for (let suggestion of suggestions.suggestions) {
const item = typeConverters.Suggest.to(suggestions, position, suggestion);
items.push(item);
}
for (let suggestions of value) {
incomplete = suggestions.incomplete || incomplete;
for (let suggestion of suggestions.suggestions) {
const item = typeConverters.Suggest.to(suggestions, position, suggestion);
items.push(item);
}
}
return new types.CompletionList(<any>items, incomplete);
......
......@@ -825,8 +825,8 @@ suite('ExtHostLanguageFeatures', function() {
threadService.sync().then(() => {
suggest(model, { lineNumber: 1, column: 1 }, ',').then(value => {
assert.equal(value.length, 1);
let [[first]] = value;
assert.ok(value.length >= 1); // check for min because snippets and others contribute
let [first] = value;
assert.equal(first.suggestions.length, 1)
assert.equal(first.suggestions[0].codeSnippet, 'testing2')
done();
......@@ -850,8 +850,8 @@ suite('ExtHostLanguageFeatures', function() {
threadService.sync().then(() => {
suggest(model, { lineNumber: 1, column: 1 }, ',').then(value => {
assert.equal(value.length, 1);
let [[first]] = value;
assert.ok(value.length >= 1);
let [first] = value;
assert.equal(first.suggestions.length, 1)
assert.equal(first.suggestions[0].codeSnippet, 'weak-selector')
done();
......@@ -876,8 +876,8 @@ suite('ExtHostLanguageFeatures', function() {
threadService.sync().then(() => {
suggest(model, { lineNumber: 1, column: 1 }, ',').then(value => {
assert.equal(value.length, 2);
let [[first], [second]] = value;
assert.ok(value.length >= 2);
let [first, second] = value;
assert.equal(first.suggestions.length, 1)
assert.equal(first.suggestions[0].codeSnippet, 'strong-2') // last wins
assert.equal(second.suggestions[0].codeSnippet, 'strong-1')
......@@ -905,8 +905,7 @@ suite('ExtHostLanguageFeatures', function() {
threadService.sync().then(() => {
suggest(model, { lineNumber: 1, column: 1 }, ',').then(value => {
assert.equal(value.length, 1);
assert.equal(value[0][0].incomplete, undefined);
assert.equal(value[0].incomplete, undefined);
done();
});
});
......@@ -923,8 +922,7 @@ suite('ExtHostLanguageFeatures', function() {
return threadService.sync().then(() => {
suggest(model, { lineNumber: 1, column: 1 }, ',').then(value => {
assert.equal(value.length, 1);
assert.equal(value[0][0].incomplete, true);
assert.equal(value[0].incomplete, true);
});
});
});
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册