提交 f5763050 编写于 作者: J Johannes Rieken

implement different memory strategies, #41060

上级 a3ed7752
......@@ -95,7 +95,7 @@ export class SuggestController implements IEditorContribution {
@IInstantiationService private _instantiationService: IInstantiationService,
) {
this._model = new SuggestModel(this._editor);
this._memory = _instantiationService.createInstance(SuggestMemories);
this._memory = _instantiationService.createInstance(SuggestMemories, 'shy');
this._toDispose.push(this._model.onDidTrigger(e => {
if (!this._widget) {
......@@ -103,21 +103,13 @@ export class SuggestController implements IEditorContribution {
}
this._widget.showTriggered(e.auto);
}));
let lastSelectedItem: ICompletionItem;
this._toDispose.push(this._model.onDidSuggest(e => {
let index = this._memory.select(this._editor.getModel().getLanguageIdentifier(), e.completionModel.items, lastSelectedItem);
if (index >= 0) {
lastSelectedItem = e.completionModel.items[index];
} else {
index = 0;
lastSelectedItem = undefined;
}
let index = this._memory.select(this._editor.getModel(), this._editor.getPosition(), e.completionModel.items);
this._widget.showSuggestions(e.completionModel, index, e.isFrozen, e.auto);
}));
this._toDispose.push(this._model.onDidCancel(e => {
if (this._widget && !e.retrigger) {
this._widget.hideWidget();
lastSelectedItem = undefined;
}
}));
......@@ -209,12 +201,8 @@ export class SuggestController implements IEditorContribution {
this._editor.pushUndoStop();
}
// remember this suggestion for future invocations
// when it wasn't the first suggestion but from the group
// of top suggestions (cons -> const, console, constructor)
if (event.model.items[0].score === event.item.score) {
this._memory.remember(this._editor.getModel().getLanguageIdentifier(), event.item);
}
// keep item in memory
this._memory.memorize(this._editor.getModel(), this._editor.getPosition(), event.item);
let { insertText } = suggestion;
if (suggestion.snippetType !== 'textmate') {
......
......@@ -5,119 +5,211 @@
'use strict';
import { ICompletionItem } from 'vs/editor/contrib/suggest/completionModel';
import { LRUCache } from 'vs/base/common/map';
import { LanguageIdentifier } from 'vs/editor/common/modes';
import { LRUCache, TernarySearchTree } from 'vs/base/common/map';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ITextModel } from 'vs/editor/common/model';
import { IPosition } from 'vs/editor/common/core/position';
import { RunOnceScheduler } from 'vs/base/common/async';
export class SuggestMemories {
export abstract class Memory {
private readonly _storagePrefix = 'suggest/memories';
private readonly _data = new Map<string, SuggestMemory>();
abstract memorize(model: ITextModel, pos: IPosition, item: ICompletionItem): void;
constructor(
@IStorageService private _storageService: IStorageService
) {
abstract select(model: ITextModel, pos: IPosition, items: ICompletionItem[]): number;
abstract toJSON(): object;
abstract fromJSON(data: object): void;
}
export class NoMemory extends Memory {
memorize(model: ITextModel, pos: IPosition, item: ICompletionItem): void {
// no-op
}
select(model: ITextModel, pos: IPosition, items: ICompletionItem[]): number {
return 0;
}
toJSON() {
return undefined;
}
fromJSON() {
//
}
}
remember({ language }: LanguageIdentifier, item: ICompletionItem): void {
let memory = this._data.get(language);
if (!memory) {
memory = new SuggestMemory();
this._data.set(language, memory);
}
memory.remember(item);
this._storageService.store(`${this._storagePrefix}/${language}`, JSON.stringify(memory), StorageScope.WORKSPACE);
}
select({ language }: LanguageIdentifier, items: ICompletionItem[], last: ICompletionItem): number {
let memory = this._data.get(language);
if (!memory) {
const key: string = `${this._storagePrefix}/${language}`;
const raw = this._storageService.get(key, StorageScope.WORKSPACE);
if (raw) {
try {
const tuples = <[string, MemoryItem][]>JSON.parse(raw);
memory = new SuggestMemory(tuples);
last = undefined;
this._data.set(language, memory);
} catch (e) {
this._storageService.remove(key, StorageScope.WORKSPACE);
export interface MemItem {
type: string;
insertText: string;
touch: number;
}
export class ShyMemory extends Memory {
private _cache = new LRUCache<string, MemItem>(300, .66);
private _seq = 0;
memorize(model: ITextModel, pos: IPosition, item: ICompletionItem): void {
const { label } = item.suggestion;
const key = `${model.getLanguageIdentifier().id}/${label}`;
this._cache.set(key, {
touch: this._seq++,
type: item.suggestion.type,
insertText: undefined
});
}
select(model: ITextModel, pos: IPosition, items: ICompletionItem[]): number {
// in order of completions, select the first
// that has been used in the past
let { word } = model.getWordUntilPosition(pos);
let res = 0;
let seq = -1;
if (word.length === 0) {
for (let i = 0; i < items.length; i++) {
const { suggestion } = items[i];
const key = `${model.getLanguageIdentifier().id}/${suggestion.label}`;
const item = this._cache.get(key);
if (item && item.touch > seq && item.type === suggestion.type) {
seq = item.touch;
res = i;
}
}
}
if (memory) {
return memory.select(items, last);
} else {
return -1;
return res;
}
toJSON(): object {
let data: [string, MemItem][] = [];
this._cache.forEach((value, key) => {
data.push([key, value]);
});
return data;
}
fromJSON(data: [string, MemItem][]): void {
this._cache.clear();
let seq = 0;
for (const [key, value] of data) {
value.touch = seq;
this._cache.set(key, value);
}
this._seq = this._cache.size;
}
}
export interface MemoryItem {
type: string;
insertText: string;
}
export class PrefixMemory extends Memory {
export class SuggestMemory {
private _trie = TernarySearchTree.forStrings<MemItem>();
private _seq = 0;
private readonly _memory = new LRUCache<string, MemoryItem>(400, 0.75);
memorize(model: ITextModel, pos: IPosition, item: ICompletionItem): void {
const { word } = model.getWordUntilPosition(pos);
const key = `${model.getLanguageIdentifier().id}/${word}`;
this._trie.set(key, {
type: item.suggestion.type,
insertText: item.suggestion.insertText,
touch: this._seq++
});
}
constructor(tuples?: [string, MemoryItem][]) {
if (tuples) {
for (const [word, item] of tuples) {
this._memory.set(word, item);
select(model: ITextModel, pos: IPosition, items: ICompletionItem[]): number {
let { word } = model.getWordUntilPosition(pos);
if (!word) {
return 0;
}
let key = `${model.getLanguageIdentifier().id}/${word}`;
let item = this._trie.get(key);
if (!item) {
item = this._trie.findSubstr(key);
}
if (item) {
for (let i = 0; i < items.length; i++) {
let { type, insertText } = items[i].suggestion;
if (type === item.type && insertText === item.insertText) {
return i;
}
}
}
return 0;
}
remember(item: ICompletionItem): void {
if (item.word) {
this._memory.set(item.word, { insertText: item.suggestion.insertText, type: item.suggestion.type });
toJSON(): object {
let entries: [string, MemItem][] = [];
this._trie.forEach((value, key) => entries.push([key, value]));
// sort by last recently used (touch), then
// take the top 200 item and normalize their
// touch
entries
.sort((a, b) => -(a[1].touch - b[1].touch))
.slice(0, 200)
.forEach((value, i) => value[1].touch = i);
return entries;
}
fromJSON(data: [string, MemItem][]): void {
this._trie.clear();
if (data.length > 0) {
this._seq = data[0][1].touch + 1;
for (const [key, value] of data) {
this._trie.set(key, value);
}
}
}
}
select(items: ICompletionItem[], last: ICompletionItem): number {
export type MemMode = 'off' | 'shy' | 'always';
if (items.length === 0) {
return -1;
}
export class SuggestMemories {
const topScore = items[0].score;
private readonly _storagePrefix = 'suggest/memories';
for (let i = 0; i < items.length; i++) {
private _mode: MemMode;
private _strategy: Memory;
private _persistSoon: RunOnceScheduler;
if (topScore !== items[i].score) {
// we only take a look at the bucket
// of top matches, hence we return
// as soon as we see an item that
// hasn't the top score anymore
return -1;
}
constructor(
mode: MemMode,
@IStorageService private _storageService: IStorageService
) {
this._persistSoon = new RunOnceScheduler(() => this._flush(), 3000);
this.setMode(mode);
}
if (items[i] === last) {
// prefer the last selected item when
// there is one
return i;
}
if (items[i].word) {
const item = this._memory.get(items[i].word);
if (this._matches(item, items[i])) {
return i;
}
}
setMode(mode: MemMode): void {
if (this._mode === mode) {
return;
}
return -1;
this._mode = mode;
this._strategy = mode === 'always' ? new PrefixMemory() : mode === 'shy' ? new ShyMemory() : new NoMemory();
try {
const raw = this._storageService.get(`${this._storagePrefix}/${this._mode}`, StorageScope.WORKSPACE);
this._strategy.fromJSON(JSON.parse(raw));
} catch (e) {
// things can go wrong with JSON...
}
}
memorize(model: ITextModel, pos: IPosition, item: ICompletionItem): void {
this._strategy.memorize(model, pos, item);
this._persistSoon.schedule();
}
private _matches(item: MemoryItem, candidate: ICompletionItem): boolean {
return item && item.insertText === candidate.suggestion.insertText && item.type === candidate.suggestion.type;
select(model: ITextModel, pos: IPosition, items: ICompletionItem[]): number {
return this._strategy.select(model, pos, items);
}
toJSON(): [string, MemoryItem][] {
const tuples: [string, MemoryItem][] = [];
this._memory.forEach((value, key) => tuples.push([key, value]));
return tuples;
private _flush() {
const raw = JSON.stringify(this._strategy);
this._storageService.store(`${this._storagePrefix}/${this._mode}`, raw, StorageScope.WORKSPACE);
}
}
......@@ -11,37 +11,37 @@ import { CompletionModel } from 'vs/editor/contrib/suggest/completionModel';
import { IPosition } from 'vs/editor/common/core/position';
import { TPromise } from 'vs/base/common/winjs.base';
suite('CompletionModel', function () {
function createSuggestItem(label: string, overwriteBefore: number, type: SuggestionType = 'property', incomplete: boolean = false, position: IPosition = { lineNumber: 1, column: 1 }): ISuggestionItem {
export function createSuggestItem(label: string, overwriteBefore: number, type: SuggestionType = 'property', incomplete: boolean = false, position: IPosition = { lineNumber: 1, column: 1 }): ISuggestionItem {
return new class implements ISuggestionItem {
return new class implements ISuggestionItem {
position = position;
position = position;
suggestion: ISuggestion = {
label,
overwriteBefore,
insertText: label,
type
};
container: ISuggestResult = {
incomplete,
suggestions: [this.suggestion]
};
suggestion: ISuggestion = {
label,
overwriteBefore,
insertText: label,
type
};
support: ISuggestSupport = {
provideCompletionItems(): any {
return;
}
};
container: ISuggestResult = {
incomplete,
suggestions: [this.suggestion]
};
resolve(): TPromise<void> {
return null;
support: ISuggestSupport = {
provideCompletionItems(): any {
return;
}
};
}
resolve(): TPromise<void> {
return null;
}
};
}
suite('CompletionModel', function () {
let model: CompletionModel;
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import { ShyMemory, NoMemory, PrefixMemory } from 'vs/editor/contrib/suggest/suggestMemory';
import { ITextModel } from 'vs/editor/common/model';
import { TextModel } from 'vs/editor/common/model/textModel';
import { ICompletionItem } from 'vs/editor/contrib/suggest/completionModel';
import { createSuggestItem } from 'vs/editor/contrib/suggest/test/completionModel.test';
import { IPosition } from 'vs/editor/common/core/position';
suite('SuggestMemories', function () {
let pos: IPosition;
let buffer: ITextModel;
let items: ICompletionItem[];
setup(function () {
pos = { lineNumber: 1, column: 1 };
buffer = TextModel.createFromString('This is some text');
items = [
createSuggestItem('foo', 0),
createSuggestItem('bar', 0)
];
});
test('NoMemory', function () {
const mem = new NoMemory();
assert.equal(mem.select(buffer, pos, items), 0);
assert.equal(mem.select(buffer, pos, []), 0);
mem.memorize(buffer, pos, items[0]);
mem.memorize(buffer, pos, null);
});
test('ShyMemories', function () {
const mem = new ShyMemory();
mem.memorize(buffer, pos, items[1]);
assert.equal(mem.select(buffer, pos, items), 1);
assert.equal(mem.select(buffer, { lineNumber: 1, column: 3 }, items), 0);
mem.memorize(buffer, pos, items[0]);
assert.equal(mem.select(buffer, pos, items), 0);
assert.equal(mem.select(buffer, pos, [
createSuggestItem('new', 0),
createSuggestItem('bar', 0)
]), 1);
assert.equal(mem.select(buffer, pos, [
createSuggestItem('new1', 0),
createSuggestItem('new2', 0)
]), 0);
});
test('PrefixMemory', function () {
const mem = new PrefixMemory();
buffer.setValue('constructor');
const item0 = createSuggestItem('console', 0);
const item1 = createSuggestItem('const', 0);
const item2 = createSuggestItem('constructor', 0);
const item3 = createSuggestItem('constant', 0);
const items = [item0, item1, item2, item3];
mem.memorize(buffer, { lineNumber: 1, column: 2 }, item1); // c -> const
mem.memorize(buffer, { lineNumber: 1, column: 3 }, item0); // co -> console
mem.memorize(buffer, { lineNumber: 1, column: 4 }, item2); // con -> constructor
assert.equal(mem.select(buffer, { lineNumber: 1, column: 1 }, items), 0);
assert.equal(mem.select(buffer, { lineNumber: 1, column: 2 }, items), 1);
assert.equal(mem.select(buffer, { lineNumber: 1, column: 3 }, items), 0);
assert.equal(mem.select(buffer, { lineNumber: 1, column: 4 }, items), 2);
assert.equal(mem.select(buffer, { lineNumber: 1, column: 7 }, items), 2); // find substr
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册