Initial VS Code 1.19 source merge (#571)

* Initial 1.19 xcopy

* Fix yarn build

* Fix numerous build breaks

* Next batch of build break fixes

* More build break fixes

* Runtime breaks

* Additional post merge fixes

* Fix windows setup file

* Fix test failures.

* Update license header blocks to refer to source eula
This commit is contained in:
Karl Burtram
2018-01-28 23:37:17 -08:00
committed by GitHub
parent 9a1ac20710
commit 251ae01c3e
8009 changed files with 93378 additions and 35634 deletions

View File

@@ -5,7 +5,7 @@
'use strict';
import { fuzzyScore } from 'vs/base/common/filters';
import { fuzzyScore, fuzzyScoreGracefulAggressive } from 'vs/base/common/filters';
import { ISuggestSupport, ISuggestResult } from 'vs/editor/common/modes';
import { ISuggestionItem, SnippetConfig } from './suggest';
import { isDisposable } from 'vs/base/common/lifecycle';
@@ -14,6 +14,7 @@ export interface ICompletionItem extends ISuggestionItem {
matches?: number[];
score?: number;
idx?: number;
word?: string;
}
@@ -37,13 +38,20 @@ export class LineContext {
characterCountDelta: number;
}
const enum Refilter {
Nothing = 0,
All = 1,
Incr = 2
}
export class CompletionModel {
private readonly _column: number;
private readonly _items: ISuggestionItem[];
private readonly _items: ICompletionItem[];
private readonly _snippetCompareFn = CompletionModel._compareCompletionItems;
private _lineContext: LineContext;
private _refilterKind: Refilter;
private _filteredItems: ICompletionItem[];
private _isIncomplete: boolean;
private _stats: ICompletionStats;
@@ -51,6 +59,7 @@ export class CompletionModel {
constructor(items: ISuggestionItem[], column: number, lineContext: LineContext, snippetConfig?: SnippetConfig) {
this._items = items;
this._column = column;
this._refilterKind = Refilter.All;
this._lineContext = lineContext;
if (snippetConfig === 'top') {
@@ -78,10 +87,10 @@ export class CompletionModel {
set lineContext(value: LineContext) {
if (this._lineContext.leadingLineContent !== value.leadingLineContent
|| this._lineContext.characterCountDelta !== value.characterCountDelta) {
|| this._lineContext.characterCountDelta !== value.characterCountDelta
) {
this._refilterKind = this._lineContext.characterCountDelta < value.characterCountDelta && this._filteredItems ? Refilter.Incr : Refilter.All;
this._lineContext = value;
this._filteredItems = undefined;
}
}
@@ -116,22 +125,30 @@ export class CompletionModel {
}
private _ensureCachedState(): void {
if (!this._filteredItems) {
if (this._refilterKind !== Refilter.Nothing) {
this._createCachedState();
}
}
private _createCachedState(): void {
this._filteredItems = [];
this._isIncomplete = false;
this._stats = { suggestionCount: 0, snippetCount: 0, textCount: 0 };
const { leadingLineContent, characterCountDelta } = this._lineContext;
let word = '';
for (let i = 0; i < this._items.length; i++) {
// incrementally filter less
const source = this._refilterKind === Refilter.All ? this._items : this._filteredItems;
const target: typeof source = [];
const item = <ICompletionItem>this._items[i];
// picks a score function based on the number of
// items that we have to score/filter
const scoreFn = source.length > 2000 ? fuzzyScore : fuzzyScoreGracefulAggressive;
for (let i = 0; i < source.length; i++) {
const item = source[i];
const { suggestion, container } = item;
// collect those supports that signaled having
@@ -140,12 +157,16 @@ export class CompletionModel {
// 'word' is that remainder of the current line that we
// filter and score against. In theory each suggestion uses a
// differnet word, but in practice not - that's why we cache
// different word, but in practice not - that's why we cache
const wordLen = suggestion.overwriteBefore + characterCountDelta - (item.position.column - this._column);
if (word.length !== wordLen) {
word = wordLen === 0 ? '' : leadingLineContent.slice(-wordLen);
}
// remember the word against which this item was
// scored
item.word = word;
if (wordLen === 0) {
// when there is nothing to score against, don't
// event try to do. Use a const rank and rely on
@@ -159,19 +180,19 @@ export class CompletionModel {
// if it matches we check with the label to compute highlights
// and if that doesn't yield a result we have no highlights,
// despite having the match
let match = fuzzyScore(word, suggestion.filterText, suggestion.overwriteBefore);
let match = scoreFn(word, suggestion.filterText, suggestion.overwriteBefore);
if (!match) {
continue;
}
item.score = match[0];
item.matches = [];
match = fuzzyScore(word, suggestion.label, suggestion.overwriteBefore);
match = scoreFn(word, suggestion.label, suggestion.overwriteBefore);
if (match) {
item.matches = match[1];
}
} else {
// by default match `word` against the `label`
let match = fuzzyScore(word, suggestion.label, suggestion.overwriteBefore);
let match = scoreFn(word, suggestion.label, suggestion.overwriteBefore);
if (match) {
item.score = match[0];
item.matches = match[1];
@@ -182,7 +203,7 @@ export class CompletionModel {
item.idx = i;
this._filteredItems.push(item);
target.push(item);
// update stats
this._stats.suggestionCount++;
@@ -192,7 +213,8 @@ export class CompletionModel {
}
}
this._filteredItems.sort(this._snippetCompareFn);
this._filteredItems = target.sort(this._snippetCompareFn);
this._refilterKind = Refilter.Nothing;
}
private static _compareCompletionItems(a: ICompletionItem, b: ICompletionItem): number {

View File

Before

Width:  |  Height:  |  Size: 584 B

After

Width:  |  Height:  |  Size: 584 B

View File

Before

Width:  |  Height:  |  Size: 584 B

After

Width:  |  Height:  |  Size: 584 B

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 823 B

After

Width:  |  Height:  |  Size: 823 B

View File

Before

Width:  |  Height:  |  Size: 823 B

After

Width:  |  Height:  |  Size: 823 B

View File

Before

Width:  |  Height:  |  Size: 552 B

After

Width:  |  Height:  |  Size: 552 B

View File

Before

Width:  |  Height:  |  Size: 552 B

After

Width:  |  Height:  |  Size: 552 B

View File

Before

Width:  |  Height:  |  Size: 664 B

After

Width:  |  Height:  |  Size: 664 B

View File

Before

Width:  |  Height:  |  Size: 664 B

After

Width:  |  Height:  |  Size: 664 B

View File

Before

Width:  |  Height:  |  Size: 789 B

After

Width:  |  Height:  |  Size: 789 B

View File

Before

Width:  |  Height:  |  Size: 789 B

After

Width:  |  Height:  |  Size: 789 B

View File

Before

Width:  |  Height:  |  Size: 499 B

After

Width:  |  Height:  |  Size: 499 B

View File

Before

Width:  |  Height:  |  Size: 579 B

After

Width:  |  Height:  |  Size: 579 B

View File

Before

Width:  |  Height:  |  Size: 579 B

After

Width:  |  Height:  |  Size: 579 B

View File

Before

Width:  |  Height:  |  Size: 750 B

After

Width:  |  Height:  |  Size: 750 B

View File

Before

Width:  |  Height:  |  Size: 760 B

After

Width:  |  Height:  |  Size: 760 B

View File

Before

Width:  |  Height:  |  Size: 929 B

After

Width:  |  Height:  |  Size: 929 B

View File

Before

Width:  |  Height:  |  Size: 589 B

After

Width:  |  Height:  |  Size: 589 B

View File

Before

Width:  |  Height:  |  Size: 923 B

After

Width:  |  Height:  |  Size: 923 B

View File

Before

Width:  |  Height:  |  Size: 923 B

After

Width:  |  Height:  |  Size: 923 B

View File

Before

Width:  |  Height:  |  Size: 831 B

After

Width:  |  Height:  |  Size: 831 B

View File

Before

Width:  |  Height:  |  Size: 831 B

After

Width:  |  Height:  |  Size: 831 B

View File

Before

Width:  |  Height:  |  Size: 594 B

After

Width:  |  Height:  |  Size: 594 B

View File

Before

Width:  |  Height:  |  Size: 594 B

After

Width:  |  Height:  |  Size: 594 B

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 710 B

After

Width:  |  Height:  |  Size: 710 B

View File

Before

Width:  |  Height:  |  Size: 922 B

After

Width:  |  Height:  |  Size: 922 B

View File

Before

Width:  |  Height:  |  Size: 922 B

After

Width:  |  Height:  |  Size: 922 B

View File

Before

Width:  |  Height:  |  Size: 577 B

After

Width:  |  Height:  |  Size: 577 B

View File

Before

Width:  |  Height:  |  Size: 577 B

After

Width:  |  Height:  |  Size: 577 B

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

Before

Width:  |  Height:  |  Size: 449 B

After

Width:  |  Height:  |  Size: 449 B

View File

Before

Width:  |  Height:  |  Size: 714 B

After

Width:  |  Height:  |  Size: 714 B

View File

Before

Width:  |  Height:  |  Size: 307 B

After

Width:  |  Height:  |  Size: 307 B

View File

Before

Width:  |  Height:  |  Size: 307 B

After

Width:  |  Height:  |  Size: 307 B

View File

Before

Width:  |  Height:  |  Size: 243 B

After

Width:  |  Height:  |  Size: 243 B

View File

@@ -10,11 +10,12 @@ import { compareIgnoreCase } from 'vs/base/common/strings';
import { assign } from 'vs/base/common/objects';
import { onUnexpectedExternalError } from 'vs/base/common/errors';
import { TPromise } from 'vs/base/common/winjs.base';
import { IModel, IEditorContribution, ICommonCodeEditor } from 'vs/editor/common/editorCommon';
import { CommonEditorRegistry } from 'vs/editor/common/editorCommonExtensions';
import { IModel, IEditorContribution } from 'vs/editor/common/editorCommon';
import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions';
import { ISuggestResult, ISuggestSupport, ISuggestion, SuggestRegistry, SuggestContext, SuggestTriggerKind } from 'vs/editor/common/modes';
import { Position, IPosition } from 'vs/editor/common/core/position';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
export const Context = {
Visible: new RawContextKey<boolean>('suggestWidgetVisible', false),
@@ -201,7 +202,7 @@ export function getSuggestionComparator(snippetConfig: SnippetConfig): (a: ISugg
}
}
CommonEditorRegistry.registerDefaultLanguageCommand('_executeCompletionItemProvider', (model, position, args) => {
registerDefaultLanguageCommand('_executeCompletionItemProvider', (model, position, args) => {
const result: ISuggestResult = {
incomplete: false,
@@ -237,11 +238,10 @@ SuggestRegistry.register('*', _provider);
* @param editor
* @param suggestions
*/
export function showSimpleSuggestions(editor: ICommonCodeEditor, suggestions: ISuggestion[]) {
export function showSimpleSuggestions(editor: ICodeEditor, suggestions: ISuggestion[]) {
setTimeout(() => {
_suggestions = suggestions;
editor.getContribution<SuggestController>('editor.contrib.suggestController').triggerSuggest([_provider]);
_suggestions = undefined;
}, 0);
}

View File

@@ -10,24 +10,24 @@ import { onUnexpectedError } from 'vs/base/common/errors';
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { ICommonCodeEditor, IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon';
import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { editorAction, ServicesAccessor, EditorAction, EditorCommand, CommonEditorRegistry } from 'vs/editor/common/editorCommonExtensions';
import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { alert } from 'vs/base/browser/ui/aria/aria';
import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Range } from 'vs/editor/common/core/range';
import { ISuggestSupport } from 'vs/editor/common/modes';
import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser';
import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2';
import { SnippetParser } from 'vs/editor/contrib/snippet/snippetParser';
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
import { Context as SuggestContext } from './suggest';
import { SuggestModel, State } from './suggestModel';
import { ICompletionItem } from './completionModel';
import { SuggestWidget } from './suggestWidget';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { SuggestMemories } from 'vs/editor/contrib/suggest/suggestMemory';
class AcceptOnCharacterOracle {
@@ -75,30 +75,51 @@ class AcceptOnCharacterOracle {
}
}
@editorContribution
export class SuggestController implements IEditorContribution {
private static ID: string = 'editor.contrib.suggestController';
public static get(editor: ICommonCodeEditor): SuggestController {
public static get(editor: ICodeEditor): SuggestController {
return editor.getContribution<SuggestController>(SuggestController.ID);
}
private _model: SuggestModel;
private _widget: SuggestWidget;
private _memory: SuggestMemories;
private _toDispose: IDisposable[] = [];
constructor(
private _editor: ICodeEditor,
@ICommandService private _commandService: ICommandService,
@ITelemetryService private _telemetryService: ITelemetryService,
@IContextKeyService _contextKeyService: IContextKeyService,
@IInstantiationService _instantiationService: IInstantiationService
@IContextKeyService private _contextKeyService: IContextKeyService,
@IInstantiationService private _instantiationService: IInstantiationService,
) {
this._model = new SuggestModel(this._editor);
this._toDispose.push(this._model.onDidTrigger(e => this._widget.showTriggered(e.auto)));
this._toDispose.push(this._model.onDidSuggest(e => this._widget.showSuggestions(e.completionModel, e.isFrozen, e.auto)));
this._toDispose.push(this._model.onDidCancel(e => !e.retrigger && this._widget.hideWidget()));
this._memory = _instantiationService.createInstance(SuggestMemories);
this._toDispose.push(this._model.onDidTrigger(e => {
if (!this._widget) {
this._createSuggestWidget();
}
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;
}
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;
}
}));
// Manage the acceptSuggestionsOnEnter context key
let acceptSuggestionsOnEnter = SuggestContext.AcceptSuggestionsOnEnter.bindTo(_contextKeyService);
@@ -108,12 +129,15 @@ export class SuggestController implements IEditorContribution {
};
this._toDispose.push(this._editor.onDidChangeConfiguration((e) => updateFromConfig()));
updateFromConfig();
}
this._widget = _instantiationService.createInstance(SuggestWidget, this._editor);
private _createSuggestWidget(): void {
this._widget = this._instantiationService.createInstance(SuggestWidget, this._editor);
this._toDispose.push(this._widget.onDidSelect(this._onDidSelectItem, this));
// Wire up logic to accept a suggestion on certain characters
const autoAcceptOracle = new AcceptOnCharacterOracle(_editor, this._widget, item => this._onDidSelectItem(item));
const autoAcceptOracle = new AcceptOnCharacterOracle(this._editor, this._widget, item => this._onDidSelectItem(item));
this._toDispose.push(
autoAcceptOracle,
this._model.onDidSuggest(e => {
@@ -123,7 +147,7 @@ export class SuggestController implements IEditorContribution {
})
);
let makesTextEdit = SuggestContext.MakesTextEdit.bindTo(_contextKeyService);
let makesTextEdit = SuggestContext.MakesTextEdit.bindTo(this._contextKeyService);
this._toDispose.push(this._widget.onDidFocus(item => {
const position = this._editor.getPosition();
@@ -176,7 +200,8 @@ export class SuggestController implements IEditorContribution {
}
const { suggestion, position } = item;
const columnDelta = this._editor.getPosition().column - position.column;
const editorColumn = this._editor.getPosition().column;
const columnDelta = editorColumn - position.column;
if (Array.isArray(suggestion.additionalTextEdits)) {
this._editor.pushUndoStop();
@@ -184,6 +209,9 @@ export class SuggestController implements IEditorContribution {
this._editor.pushUndoStop();
}
// remember this word for future invocations
this._memory.remember(this._editor.getModel().getLanguageIdentifier(), item);
let { insertText } = suggestion;
if (suggestion.snippetType !== 'textmate') {
insertText = SnippetParser.escape(insertText);
@@ -210,15 +238,6 @@ export class SuggestController implements IEditorContribution {
}
this._alertCompletionItem(item);
/* __GDPR__
"suggestSnippetInsert" : {
"suggestionType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"${include}": [
"${EditorTelemetryData}"
]
}
*/
this._telemetryService.publicLog('suggestSnippetInsert', { ...this._editor.getTelemetryData(), suggestionType: suggestion.type });
}
private _alertCompletionItem({ suggestion }: ICompletionItem): void {
@@ -295,7 +314,6 @@ export class SuggestController implements IEditorContribution {
}
}
@editorAction
export class TriggerSuggestAction extends EditorAction {
static readonly id = 'editor.action.triggerSuggest';
@@ -314,7 +332,7 @@ export class TriggerSuggestAction extends EditorAction {
});
}
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void {
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
const controller = SuggestController.get(editor);
if (!controller) {
@@ -325,12 +343,15 @@ export class TriggerSuggestAction extends EditorAction {
}
}
const weight = CommonEditorRegistry.commandWeight(90);
registerEditorContribution(SuggestController);
registerEditorAction(TriggerSuggestAction);
const weight = KeybindingsRegistry.WEIGHT.editorContrib(90);
const SuggestCommand = EditorCommand.bindToContribution<SuggestController>(SuggestController.get);
CommonEditorRegistry.registerEditorCommand(new SuggestCommand({
registerEditorCommand(new SuggestCommand({
id: 'acceptSelectedSuggestion',
precondition: SuggestContext.Visible,
handler: x => x.acceptSelectedSuggestion(),
@@ -341,7 +362,7 @@ CommonEditorRegistry.registerEditorCommand(new SuggestCommand({
}
}));
CommonEditorRegistry.registerEditorCommand(new SuggestCommand({
registerEditorCommand(new SuggestCommand({
id: 'acceptSelectedSuggestionOnEnter',
precondition: SuggestContext.Visible,
handler: x => x.acceptSelectedSuggestion(),
@@ -352,7 +373,7 @@ CommonEditorRegistry.registerEditorCommand(new SuggestCommand({
}
}));
CommonEditorRegistry.registerEditorCommand(new SuggestCommand({
registerEditorCommand(new SuggestCommand({
id: 'hideSuggestWidget',
precondition: SuggestContext.Visible,
handler: x => x.cancelSuggestWidget(),
@@ -364,7 +385,7 @@ CommonEditorRegistry.registerEditorCommand(new SuggestCommand({
}
}));
CommonEditorRegistry.registerEditorCommand(new SuggestCommand({
registerEditorCommand(new SuggestCommand({
id: 'selectNextSuggestion',
precondition: ContextKeyExpr.and(SuggestContext.Visible, SuggestContext.MultipleSuggestions),
handler: c => c.selectNextSuggestion(),
@@ -377,7 +398,7 @@ CommonEditorRegistry.registerEditorCommand(new SuggestCommand({
}
}));
CommonEditorRegistry.registerEditorCommand(new SuggestCommand({
registerEditorCommand(new SuggestCommand({
id: 'selectNextPageSuggestion',
precondition: ContextKeyExpr.and(SuggestContext.Visible, SuggestContext.MultipleSuggestions),
handler: c => c.selectNextPageSuggestion(),
@@ -389,13 +410,13 @@ CommonEditorRegistry.registerEditorCommand(new SuggestCommand({
}
}));
CommonEditorRegistry.registerEditorCommand(new SuggestCommand({
registerEditorCommand(new SuggestCommand({
id: 'selectLastSuggestion',
precondition: ContextKeyExpr.and(SuggestContext.Visible, SuggestContext.MultipleSuggestions),
handler: c => c.selectLastSuggestion()
}));
CommonEditorRegistry.registerEditorCommand(new SuggestCommand({
registerEditorCommand(new SuggestCommand({
id: 'selectPrevSuggestion',
precondition: ContextKeyExpr.and(SuggestContext.Visible, SuggestContext.MultipleSuggestions),
handler: c => c.selectPrevSuggestion(),
@@ -408,7 +429,7 @@ CommonEditorRegistry.registerEditorCommand(new SuggestCommand({
}
}));
CommonEditorRegistry.registerEditorCommand(new SuggestCommand({
registerEditorCommand(new SuggestCommand({
id: 'selectPrevPageSuggestion',
precondition: ContextKeyExpr.and(SuggestContext.Visible, SuggestContext.MultipleSuggestions),
handler: c => c.selectPrevPageSuggestion(),
@@ -420,13 +441,13 @@ CommonEditorRegistry.registerEditorCommand(new SuggestCommand({
}
}));
CommonEditorRegistry.registerEditorCommand(new SuggestCommand({
registerEditorCommand(new SuggestCommand({
id: 'selectFirstSuggestion',
precondition: ContextKeyExpr.and(SuggestContext.Visible, SuggestContext.MultipleSuggestions),
handler: c => c.selectFirstSuggestion()
}));
CommonEditorRegistry.registerEditorCommand(new SuggestCommand({
registerEditorCommand(new SuggestCommand({
id: 'toggleSuggestionDetails',
precondition: SuggestContext.Visible,
handler: x => x.toggleSuggestionDetails(),
@@ -438,7 +459,7 @@ CommonEditorRegistry.registerEditorCommand(new SuggestCommand({
}
}));
CommonEditorRegistry.registerEditorCommand(new SuggestCommand({
registerEditorCommand(new SuggestCommand({
id: 'toggleSuggestionFocus',
precondition: SuggestContext.Visible,
handler: x => x.toggleSuggestionFocus(),

View File

@@ -0,0 +1,107 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'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 { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
export class SuggestMemories {
private readonly _storagePrefix = 'suggest/memories';
private readonly _data = new Map<string, SuggestMemory>();
constructor(
@IStorageService private _storageService: IStorageService
) {
//
}
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);
}
}
}
if (memory) {
return memory.select(items, last);
} else {
return -1;
}
}
}
export interface MemoryItem {
type: string;
insertText: string;
}
export class SuggestMemory {
private readonly _memory = new LRUCache<string, MemoryItem>(400, 0.75);
constructor(tuples?: [string, MemoryItem][]) {
if (tuples) {
for (const [word, item] of tuples) {
this._memory.set(word, item);
}
}
}
remember(item: ICompletionItem): void {
if (item.word) {
this._memory.set(item.word, { insertText: item.suggestion.insertText, type: item.suggestion.type });
}
}
select(items: ICompletionItem[], last: ICompletionItem): number {
for (let i = 0; i < items.length; i++) {
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;
}
}
}
return -1;
}
private _matches(item: MemoryItem, candidate: ICompletionItem): boolean {
return item && item.insertText === candidate.suggestion.insertText && item.type === candidate.suggestion.type;
}
toJSON(): [string, MemoryItem][] {
const tuples: [string, MemoryItem][] = [];
this._memory.forEach((value, key) => tuples.push([key, value]));
return tuples;
}
}

View File

@@ -10,12 +10,13 @@ import { TimeoutTimer } from 'vs/base/common/async';
import Event, { Emitter } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import { ICommonCodeEditor, IModel, IWordAtPosition } from 'vs/editor/common/editorCommon';
import { IModel, IWordAtPosition } from 'vs/editor/common/editorCommon';
import { ISuggestSupport, SuggestRegistry, StandardTokenType, SuggestTriggerKind } from 'vs/editor/common/modes';
import { Position } from 'vs/editor/common/core/position';
import { provideSuggestionItems, getSuggestionComparator, ISuggestionItem } from './suggest';
import { CompletionModel } from './completionModel';
import { CursorChangeReason, ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
export interface ICancelEvent {
retrigger: boolean;
@@ -38,7 +39,7 @@ export interface SuggestTriggerContext {
export class LineContext {
static shouldAutoTrigger(editor: ICommonCodeEditor): boolean {
static shouldAutoTrigger(editor: ICodeEditor): boolean {
const model = editor.getModel();
if (!model) {
return false;
@@ -58,7 +59,7 @@ export class LineContext {
return true;
}
static isInEditableRange(editor: ICommonCodeEditor): boolean {
static isInEditableRange(editor: ICodeEditor): boolean {
const model = editor.getModel();
const position = editor.getPosition();
if (model.hasEditableRange()) {
@@ -93,7 +94,7 @@ export const enum State {
export class SuggestModel implements IDisposable {
private _editor: ICommonCodeEditor;
private _editor: ICodeEditor;
private _toDispose: IDisposable[] = [];
private _quickSuggestDelay: number;
private _triggerCharacterListener: IDisposable;
@@ -114,7 +115,7 @@ export class SuggestModel implements IDisposable {
readonly onDidTrigger: Event<ITriggerEvent> = this._onDidTrigger.event;
readonly onDidSuggest: Event<ISuggestEvent> = this._onDidSuggest.event;
constructor(editor: ICommonCodeEditor) {
constructor(editor: ICodeEditor) {
this._editor = editor;
this._state = State.Idle;
this._triggerAutoSuggestPromise = null;
@@ -222,6 +223,8 @@ export class SuggestModel implements IDisposable {
cancel(retrigger: boolean = false): void {
this._triggerRefilter.cancel();
if (this._triggerAutoSuggestPromise) {
this._triggerAutoSuggestPromise.cancel();
this._triggerAutoSuggestPromise = null;
@@ -256,17 +259,14 @@ export class SuggestModel implements IDisposable {
this._currentPosition = this._editor.getPosition();
if (!e.selection.isEmpty()
|| e.source !== 'keyboard'
|| e.reason !== CursorChangeReason.NotSet
|| (e.source !== 'keyboard' && e.source !== 'deleteLeft')
) {
if (this._state === State.Idle) {
// Early exit if nothing needs to be done!
// Leave some form of early exit check here if you wish to continue being a cursor position change listener ;)
return;
// Early exit if nothing needs to be done!
// Leave some form of early exit check here if you wish to continue being a cursor position change listener ;)
if (this._state !== State.Idle) {
this.cancel();
}
this.cancel();
return;
}

View File

@@ -30,11 +30,11 @@ import { attachListStyler } from 'vs/platform/theme/common/styler';
import { IThemeService, ITheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { registerColor, editorWidgetBackground, listFocusBackground, activeContrastBorder, listHighlightForeground, editorForeground, editorWidgetBorder, focusBorder, textLinkForeground, textCodeBlockBackground } from 'vs/platform/theme/common/colorRegistry';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { MarkdownRenderer } from 'vs/editor/contrib/markdown/browser/markdownRenderer';
import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IOpenerService } from 'vs/platform/opener/common/opener';
const sticky = true; // for development purposes
const sticky = false; // for development purposes
const expandSuggestionDocsByDefault = false;
const maxSuggestionsToShow = 12;
@@ -153,6 +153,7 @@ class Renderer implements IRenderer<ICompletionItem, ISuggestionTemplateData> {
}
data.highlightedLabel.set(suggestion.label, createMatches(element.matches));
// data.highlightedLabel.set(`${suggestion.label} <${element.score}=score(${element.word}, ${suggestion.filterText || suggestion.label})>`, createMatches(element.matches));
data.typeLabel.textContent = (suggestion.detail || '').replace(/\n.*$/m, '');
if (canExpandCompletionItem(element)) {
@@ -222,7 +223,7 @@ class SuggestionDetails {
this.header = append(this.body, $('.header'));
this.close = append(this.header, $('span.close'));
this.close.title = nls.localize('readLess', "Read less...{0}", triggerKeybindingLabel);
this.close.title = nls.localize('readLess', "Read less...{0}", this.triggerKeybindingLabel);
this.type = append(this.header, $('p.type'));
this.docs = append(this.body, $('p.docs'));
@@ -425,7 +426,8 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
this.list = new List(this.listElement, this, [renderer], {
useShadows: false,
selectOnMouseDown: true
selectOnMouseDown: true,
focusOnMouseDown: false
});
this.toDispose = [
@@ -497,7 +499,9 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
}
const item = e.elements[0];
this.onDidSelectEmitter.fire(item);
item.resolve().then(() => {
this.onDidSelectEmitter.fire(item);
});
alert(nls.localize('suggestionAriaAccepted', "{0}, accepted", item.suggestion.label));
@@ -557,21 +561,12 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
}
this._ariaAlert(null);
// TODO@Alex: Chromium bug
// this.editor.setAriaActiveDescendant(null);
return;
}
const item = e.elements[0];
this._ariaAlert(this._getSuggestionAriaAlertLabel(item));
// TODO@Alex: Chromium bug
// // TODO@Alex: the list is not done rendering...
// setTimeout(() => {
// this.editor.setAriaActiveDescendant(this.list.getElementId(e.indexes[0]));
// }, 100);
if (item === this.focusedItem) {
return;
}
@@ -699,7 +694,7 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
}
}
showSuggestions(completionModel: CompletionModel, isFrozen: boolean, isAuto: boolean): void {
showSuggestions(completionModel: CompletionModel, selectionIndex: number, isFrozen: boolean, isAuto: boolean): void {
if (this.loadingTimeout) {
clearTimeout(this.loadingTimeout);
this.loadingTimeout = null;
@@ -743,8 +738,8 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
this.focusedItem = null;
this.focusedItemIndex = null;
this.list.splice(0, this.list.length, this.completionModel.items);
this.list.setFocus([0]);
this.list.reveal(0, 0);
this.list.setFocus([selectionIndex]);
this.list.reveal(selectionIndex, selectionIndex);
if (isFrozen) {
this.setState(State.Frozen);
@@ -1106,7 +1101,7 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
registerThemingParticipant((theme, collector) => {
let matchHighlight = theme.getColor(editorSuggestWidgetHighlightForeground);
if (matchHighlight) {
collector.addRule(`.monaco-editor .suggest-widget:not(.frozen) .monaco-list .monaco-list-row .monaco-highlighted-label .highlight { color: ${matchHighlight}; }`);
collector.addRule(`.monaco-editor .suggest-widget .monaco-list .monaco-list-row .monaco-highlighted-label .highlight { color: ${matchHighlight}; }`);
}
let foreground = theme.getColor(editorSuggestWidgetForeground);
if (foreground) {

View File

@@ -6,8 +6,8 @@
import * as assert from 'assert';
import { ISuggestion, ISuggestResult, ISuggestSupport, SuggestionType } from 'vs/editor/common/modes';
import { ISuggestionItem, getSuggestionComparator } from 'vs/editor/contrib/suggest/browser/suggest';
import { CompletionModel } from 'vs/editor/contrib/suggest/browser/completionModel';
import { ISuggestionItem, getSuggestionComparator } from 'vs/editor/contrib/suggest/suggest';
import { CompletionModel } from 'vs/editor/contrib/suggest/completionModel';
import { IPosition } from 'vs/editor/common/core/position';
import { TPromise } from 'vs/base/common/winjs.base';
@@ -225,7 +225,74 @@ suite('CompletionModel', function () {
const [first, second] = model.items;
assert.equal(first.suggestion.label, 'source');
assert.equal(second.suggestion.label, '<- groups');
});
test('Score only filtered items when typing more, score all when typing less', function () {
model = new CompletionModel([
createSuggestItem('console', 0, 'property'),
createSuggestItem('co_new', 0, 'property'),
createSuggestItem('bar', 0, 'property'),
createSuggestItem('car', 0, 'property'),
createSuggestItem('foo', 0, 'property'),
], 1, {
leadingLineContent: '',
characterCountDelta: 0
}, 'inline');
assert.equal(model.items.length, 5);
// narrow down once
model.lineContext = { leadingLineContent: 'c', characterCountDelta: 1 };
assert.equal(model.items.length, 3);
// query gets longer, narrow down the narrow-down'ed-set from before
model.lineContext = { leadingLineContent: 'cn', characterCountDelta: 2 };
assert.equal(model.items.length, 2);
// query gets shorter, refilter everything
model.lineContext = { leadingLineContent: '', characterCountDelta: 0 };
assert.equal(model.items.length, 5);
});
test('Have more relaxed suggest matching algorithm #15419', function () {
model = new CompletionModel([
createSuggestItem('result', 0, 'property'),
createSuggestItem('replyToUser', 0, 'property'),
createSuggestItem('randomLolut', 0, 'property'),
createSuggestItem('car', 0, 'property'),
createSuggestItem('foo', 0, 'property'),
], 1, {
leadingLineContent: '',
characterCountDelta: 0
}, 'inline');
// query gets longer, narrow down the narrow-down'ed-set from before
model.lineContext = { leadingLineContent: 'rlut', characterCountDelta: 4 };
assert.equal(model.items.length, 3);
const [first, second, third] = model.items;
assert.equal(first.suggestion.label, 'result'); // best with `rult`
assert.equal(second.suggestion.label, 'replyToUser'); // best with `rltu`
assert.equal(third.suggestion.label, 'randomLolut'); // best with `rlut`
});
test('Emmet suggestion not appearing at the top of the list in jsx files, #39518', function () {
model = new CompletionModel([
createSuggestItem('from', 0, 'property'),
createSuggestItem('form', 0, 'property'),
createSuggestItem('form:get', 0, 'property'),
createSuggestItem('testForeignMeasure', 0, 'property'),
createSuggestItem('fooRoom', 0, 'property'),
], 1, {
leadingLineContent: '',
characterCountDelta: 0
}, 'inline');
model.lineContext = { leadingLineContent: 'form', characterCountDelta: 4 };
assert.equal(model.items.length, 5);
const [first, second, third] = model.items;
assert.equal(first.suggestion.label, 'form'); // best with `form`
assert.equal(second.suggestion.label, 'form:get'); // best with `form`
assert.equal(third.suggestion.label, 'from'); // best with `from`
});
});

View File

@@ -8,7 +8,7 @@ import * as assert from 'assert';
import URI from 'vs/base/common/uri';
import { IDisposable } from 'vs/base/common/lifecycle';
import { SuggestRegistry } from 'vs/editor/common/modes';
import { provideSuggestionItems } from 'vs/editor/contrib/suggest/browser/suggest';
import { provideSuggestionItems } from 'vs/editor/contrib/suggest/suggest';
import { Position } from 'vs/editor/common/core/position';
import { Model } from 'vs/editor/common/model/model';

View File

@@ -10,10 +10,10 @@ import URI from 'vs/base/common/uri';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import { Model } from 'vs/editor/common/model/model';
import { ICommonCodeEditor, Handler } from 'vs/editor/common/editorCommon';
import { Handler } from 'vs/editor/common/editorCommon';
import { ISuggestSupport, ISuggestResult, SuggestRegistry, SuggestTriggerKind } from 'vs/editor/common/modes';
import { SuggestModel, LineContext } from 'vs/editor/contrib/suggest/browser/suggestModel';
import { MockCodeEditor, MockScopeLocation } from 'vs/editor/test/common/mocks/mockCodeEditor';
import { SuggestModel, LineContext } from 'vs/editor/contrib/suggest/suggestModel';
import { TestCodeEditor, MockScopeLocation } from 'vs/editor/test/browser/testCodeEditor';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
@@ -22,8 +22,10 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Range } from 'vs/editor/common/core/range';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands';
function createMockEditor(model: Model): MockCodeEditor {
function createMockEditor(model: Model): TestCodeEditor {
const contextKeyService = new MockContextKeyService();
const telemetryService = NullTelemetryService;
const instantiationService = new InstantiationService(new ServiceCollection(
@@ -31,7 +33,7 @@ function createMockEditor(model: Model): MockCodeEditor {
[ITelemetryService, telemetryService]
));
const editor = new MockCodeEditor(new MockScopeLocation(), {}, instantiationService, contextKeyService);
const editor = new TestCodeEditor(new MockScopeLocation(), {}, instantiationService, contextKeyService);
editor.setModel(model);
return editor;
}
@@ -70,8 +72,8 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
const alwaysEmptySupport: ISuggestSupport = {
provideCompletionItems(doc, pos) {
return <ISuggestResult>{
provideCompletionItems(doc, pos): ISuggestResult {
return {
incomplete: false,
suggestions: []
};
@@ -79,8 +81,8 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
};
const alwaysSomethingSupport: ISuggestSupport = {
provideCompletionItems(doc, pos) {
return <ISuggestResult>{
provideCompletionItems(doc, pos): ISuggestResult {
return {
incomplete: false,
suggestions: [{
label: doc.getWordUntilPosition(pos).word,
@@ -100,7 +102,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
disposables.push(model);
});
function withOracle(callback: (model: SuggestModel, editor: ICommonCodeEditor) => any): TPromise<any> {
function withOracle(callback: (model: SuggestModel, editor: ICodeEditor) => any): TPromise<any> {
return new TPromise((resolve, reject) => {
const editor = createMockEditor(model);
@@ -222,9 +224,8 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
test('#17400: Keep filtering suggestModel.ts after space', function () {
disposables.push(SuggestRegistry.register({ scheme: 'test' }, {
provideCompletionItems(doc, pos) {
return <ISuggestResult>{
currentWord: '',
provideCompletionItems(doc, pos): ISuggestResult {
return {
incomplete: false,
suggestions: [{
label: 'My Table',
@@ -272,9 +273,8 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
test('#21484: Trigger character always force a new completion session', function () {
disposables.push(SuggestRegistry.register({ scheme: 'test' }, {
provideCompletionItems(doc, pos) {
return <ISuggestResult>{
currentWord: '',
provideCompletionItems(doc, pos): ISuggestResult {
return {
incomplete: false,
suggestions: [{
label: 'foo.bar',
@@ -288,9 +288,8 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
disposables.push(SuggestRegistry.register({ scheme: 'test' }, {
triggerCharacters: ['.'],
provideCompletionItems(doc, pos) {
return <ISuggestResult>{
currentWord: '',
provideCompletionItems(doc, pos): ISuggestResult {
return {
incomplete: false,
suggestions: [{
label: 'boom',
@@ -383,8 +382,8 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
test('Incomplete suggestion results cause re-triggering when typing w/o further context, #28400 (1/2)', function () {
disposables.push(SuggestRegistry.register({ scheme: 'test' }, {
provideCompletionItems(doc, pos) {
return <ISuggestResult>{
provideCompletionItems(doc, pos): ISuggestResult {
return {
incomplete: true,
suggestions: [{
label: 'foo',
@@ -420,8 +419,8 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
test('Incomplete suggestion results cause re-triggering when typing w/o further context, #28400 (2/2)', function () {
disposables.push(SuggestRegistry.register({ scheme: 'test' }, {
provideCompletionItems(doc, pos) {
return <ISuggestResult>{
provideCompletionItems(doc, pos): ISuggestResult {
return {
incomplete: true,
suggestions: [{
label: 'foo;',
@@ -464,11 +463,10 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
let triggerCharacter = '';
disposables.push(SuggestRegistry.register({ scheme: 'test' }, {
triggerCharacters: ['.'],
provideCompletionItems(doc, pos, context) {
provideCompletionItems(doc, pos, context): ISuggestResult {
assert.equal(context.triggerKind, SuggestTriggerKind.TriggerCharacter);
triggerCharacter = context.triggerCharacter;
return <ISuggestResult>{
currentWord: '',
return {
incomplete: false,
suggestions: [
{
@@ -497,8 +495,8 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
test('Mac press and hold accent character insertion does not update suggestions, #35269', function () {
disposables.push(SuggestRegistry.register({ scheme: 'test' }, {
provideCompletionItems(doc, pos) {
return <ISuggestResult>{
provideCompletionItems(doc, pos): ISuggestResult {
return {
incomplete: true,
suggestions: [{
label: 'abc',
@@ -537,4 +535,33 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
});
});
});
test('Backspace should not always cancel code completion, #36491', function () {
disposables.push(SuggestRegistry.register({ scheme: 'test' }, alwaysSomethingSupport));
return withOracle(async (model, editor) => {
await assertEvent(model.onDidSuggest, () => {
editor.setPosition({ lineNumber: 1, column: 4 });
editor.trigger('keyboard', Handler.Type, { text: 'd' });
}, event => {
assert.equal(event.auto, true);
assert.equal(event.completionModel.items.length, 1);
const [first] = event.completionModel.items;
assert.equal(first.support, alwaysSomethingSupport);
});
await assertEvent(model.onDidSuggest, () => {
CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null);
}, event => {
assert.equal(event.auto, true);
assert.equal(event.completionModel.items.length, 1);
const [first] = event.completionModel.items;
assert.equal(first.support, alwaysSomethingSupport);
});
});
});
});