Merge VS Code 1.21 source code (#1067)

* Initial VS Code 1.21 file copy with patches

* A few more merges

* Post npm install

* Fix batch of build breaks

* Fix more build breaks

* Fix more build errors

* Fix more build breaks

* Runtime fixes 1

* Get connection dialog working with some todos

* Fix a few packaging issues

* Copy several node_modules to package build to fix loader issues

* Fix breaks from master

* A few more fixes

* Make tests pass

* First pass of license header updates

* Second pass of license header updates

* Fix restore dialog issues

* Remove add additional themes menu items

* fix select box issues where the list doesn't show up

* formatting

* Fix editor dispose issue

* Copy over node modules to correct location on all platforms
This commit is contained in:
Karl Burtram
2018-04-04 15:27:51 -07:00
committed by GitHub
parent 5fba3e31b4
commit dafb780987
9412 changed files with 141255 additions and 98813 deletions

View File

@@ -5,7 +5,7 @@
'use strict';
import { fuzzyScore, fuzzyScoreGracefulAggressive } from 'vs/base/common/filters';
import { fuzzyScore, fuzzyScoreGracefulAggressive, skipScore } 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';
@@ -185,11 +185,8 @@ export class CompletionModel {
continue;
}
item.score = match[0];
item.matches = [];
match = scoreFn(word, suggestion.label, suggestion.overwriteBefore);
if (match) {
item.matches = match[1];
}
item.matches = skipScore(word, suggestion.label)[1];
} else {
// by default match `word` against the `label`
let match = scoreFn(word, suggestion.label, suggestion.overwriteBefore);

View File

@@ -10,7 +10,8 @@ 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 } from 'vs/editor/common/editorCommon';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
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';
@@ -43,7 +44,7 @@ export function setSnippetSuggestSupport(support: ISuggestSupport): ISuggestSupp
return old;
}
export function provideSuggestionItems(model: IModel, position: Position, snippetConfig: SnippetConfig = 'bottom', onlyFrom?: ISuggestSupport[], context?: SuggestContext): TPromise<ISuggestionItem[]> {
export function provideSuggestionItems(model: ITextModel, position: Position, snippetConfig: SnippetConfig = 'bottom', onlyFrom?: ISuggestSupport[], context?: SuggestContext): TPromise<ISuggestionItem[]> {
const allSuggestions: ISuggestionItem[] = [];
const acceptSuggestion = createSuggesionFilter(snippetConfig);
@@ -127,7 +128,7 @@ function fixOverwriteBeforeAfter(suggestion: ISuggestion, container: ISuggestRes
}
}
function createSuggestionResolver(provider: ISuggestSupport, suggestion: ISuggestion, model: IModel, position: Position): () => TPromise<void> {
function createSuggestionResolver(provider: ISuggestSupport, suggestion: ISuggestion, model: ITextModel, position: Position): () => TPromise<void> {
return () => {
if (typeof provider.resolveCompletionItem === 'function') {
return asWinJsPromise(token => provider.resolveCompletionItem(model, position, suggestion, token))
@@ -233,11 +234,6 @@ let _provider = new class implements ISuggestSupport {
SuggestRegistry.register('*', _provider);
/**
*
* @param editor
* @param suggestions
*/
export function showSimpleSuggestions(editor: ICodeEditor, suggestions: ISuggestion[]) {
setTimeout(() => {
_suggestions = suggestions;

View File

@@ -25,7 +25,7 @@ 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 { SuggestWidget, ISelectedSuggestion } from './suggestWidget';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { SuggestMemories } from 'vs/editor/contrib/suggest/suggestMemory';
@@ -34,9 +34,9 @@ class AcceptOnCharacterOracle {
private _disposables: IDisposable[] = [];
private _activeAcceptCharacters = new Set<string>();
private _activeItem: ICompletionItem;
private _activeItem: ISelectedSuggestion;
constructor(editor: ICodeEditor, widget: SuggestWidget, accept: (item: ICompletionItem) => any) {
constructor(editor: ICodeEditor, widget: SuggestWidget, accept: (selected: ISelectedSuggestion) => any) {
this._disposables.push(widget.onDidShow(() => this._onItem(widget.getFocusedItem())));
this._disposables.push(widget.onDidFocus(this._onItem, this));
@@ -52,14 +52,14 @@ class AcceptOnCharacterOracle {
}));
}
private _onItem(item: ICompletionItem): void {
if (!item || isFalsyOrEmpty(item.suggestion.commitCharacters)) {
private _onItem(selected: ISelectedSuggestion): void {
if (!selected || isFalsyOrEmpty(selected.item.suggestion.commitCharacters)) {
this.reset();
return;
}
this._activeItem = item;
this._activeItem = selected;
this._activeAcceptCharacters.clear();
for (const ch of item.suggestion.commitCharacters) {
for (const ch of selected.item.suggestion.commitCharacters) {
if (ch.length > 0) {
this._activeAcceptCharacters.add(ch[0]);
}
@@ -77,7 +77,7 @@ class AcceptOnCharacterOracle {
export class SuggestController implements IEditorContribution {
private static ID: string = 'editor.contrib.suggestController';
private static readonly ID: string = 'editor.contrib.suggestController';
public static get(editor: ICodeEditor): SuggestController {
return editor.getContribution<SuggestController>(SuggestController.ID);
@@ -90,12 +90,12 @@ export class SuggestController implements IEditorContribution {
constructor(
private _editor: ICodeEditor,
@ICommandService private _commandService: ICommandService,
@IContextKeyService private _contextKeyService: IContextKeyService,
@IInstantiationService private _instantiationService: IInstantiationService,
@ICommandService private readonly _commandService: ICommandService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
) {
this._model = new SuggestModel(this._editor);
this._memory = _instantiationService.createInstance(SuggestMemories);
this._memory = _instantiationService.createInstance(SuggestMemories, this._editor.getConfiguration().contribInfo.suggestSelection);
this._toDispose.push(this._model.onDidTrigger(e => {
if (!this._widget) {
@@ -103,29 +103,22 @@ 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;
}
}));
// Manage the acceptSuggestionsOnEnter context key
let acceptSuggestionsOnEnter = SuggestContext.AcceptSuggestionsOnEnter.bindTo(_contextKeyService);
let updateFromConfig = () => {
const { acceptSuggestionOnEnter } = this._editor.getConfiguration().contribInfo;
const { acceptSuggestionOnEnter, suggestSelection } = this._editor.getConfiguration().contribInfo;
acceptSuggestionsOnEnter.set(acceptSuggestionOnEnter === 'on' || acceptSuggestionOnEnter === 'smart');
this._memory.setMode(suggestSelection);
};
this._toDispose.push(this._editor.onDidChangeConfiguration((e) => updateFromConfig()));
updateFromConfig();
@@ -148,7 +141,7 @@ export class SuggestController implements IEditorContribution {
);
let makesTextEdit = SuggestContext.MakesTextEdit.bindTo(this._contextKeyService);
this._toDispose.push(this._widget.onDidFocus(item => {
this._toDispose.push(this._widget.onDidFocus(({ item }) => {
const position = this._editor.getPosition();
const startColumn = item.position.column - item.suggestion.overwriteBefore;
@@ -193,13 +186,13 @@ export class SuggestController implements IEditorContribution {
}
}
private _onDidSelectItem(item: ICompletionItem): void {
if (!item) {
protected _onDidSelectItem(event: ISelectedSuggestion): void {
if (!event || !event.item) {
this._model.cancel();
return;
}
const { suggestion, position } = item;
const { suggestion, position } = event.item;
const editorColumn = this._editor.getPosition().column;
const columnDelta = editorColumn - position.column;
@@ -209,8 +202,8 @@ export class SuggestController implements IEditorContribution {
this._editor.pushUndoStop();
}
// remember this word for future invocations
this._memory.remember(this._editor.getModel().getLanguageIdentifier(), item);
// keep item in memory
this._memory.memorize(this._editor.getModel(), this._editor.getPosition(), event.item);
let { insertText } = suggestion;
if (suggestion.snippetType !== 'textmate') {
@@ -237,7 +230,7 @@ export class SuggestController implements IEditorContribution {
this._model.cancel();
}
this._alertCompletionItem(item);
this._alertCompletionItem(event.item);
}
private _alertCompletionItem({ suggestion }: ICompletionItem): void {

View File

@@ -5,103 +5,210 @@
'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);
export interface MemItem {
type: string;
insertText: string;
touch: number;
}
export class LRUMemory 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().language}/${label}`;
this._cache.set(key, {
touch: this._seq++,
type: item.suggestion.type,
insertText: undefined
});
}
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);
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().language}/${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);
constructor(tuples?: [string, MemoryItem][]) {
if (tuples) {
for (const [word, item] of tuples) {
this._memory.set(word, item);
}
}
memorize(model: ITextModel, pos: IPosition, item: ICompletionItem): void {
const { word } = model.getWordUntilPosition(pos);
const key = `${model.getLanguageIdentifier().language}/${word}`;
this._trie.set(key, {
type: item.suggestion.type,
insertText: item.suggestion.insertText,
touch: this._seq++
});
}
remember(item: ICompletionItem): void {
if (item.word) {
this._memory.set(item.word, { insertText: item.suggestion.insertText, type: item.suggestion.type });
select(model: ITextModel, pos: IPosition, items: ICompletionItem[]): number {
let { word } = model.getWordUntilPosition(pos);
if (!word) {
return 0;
}
}
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])) {
let key = `${model.getLanguageIdentifier().language}/${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 -1;
return 0;
}
private _matches(item: MemoryItem, candidate: ICompletionItem): boolean {
return item && item.insertText === candidate.suggestion.insertText && item.type === candidate.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))
.forEach((value, i) => value[1].touch = i);
return entries.slice(0, 200);
}
toJSON(): [string, MemoryItem][] {
const tuples: [string, MemoryItem][] = [];
this._memory.forEach((value, key) => tuples.push([key, value]));
return tuples;
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);
}
}
}
}
export type MemMode = 'first' | 'recentlyUsed' | 'recentlyUsedByPrefix';
export class SuggestMemories {
private readonly _storagePrefix = 'suggest/memories';
private _mode: MemMode;
private _strategy: Memory;
private _persistSoon: RunOnceScheduler;
constructor(
mode: MemMode,
@IStorageService private readonly _storageService: IStorageService
) {
this._persistSoon = new RunOnceScheduler(() => this._flush(), 3000);
this.setMode(mode);
}
setMode(mode: MemMode): void {
if (this._mode === mode) {
return;
}
this._mode = mode;
this._strategy = mode === 'recentlyUsedByPrefix' ? new PrefixMemory() : mode === 'recentlyUsed' ? new LRUMemory() : 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();
}
select(model: ITextModel, pos: IPosition, items: ICompletionItem[]): number {
return this._strategy.select(model, pos, items);
}
private _flush() {
const raw = JSON.stringify(this._strategy);
this._storageService.store(`${this._storagePrefix}/${this._mode}`, raw, StorageScope.WORKSPACE);
}
}

View File

@@ -10,8 +10,8 @@ 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 { IModel, IWordAtPosition } from 'vs/editor/common/editorCommon';
import { ISuggestSupport, SuggestRegistry, StandardTokenType, SuggestTriggerKind } from 'vs/editor/common/modes';
import { ITextModel, IWordAtPosition } from 'vs/editor/common/model';
import { ISuggestSupport, SuggestRegistry, StandardTokenType, SuggestTriggerKind, SuggestContext } from 'vs/editor/common/modes';
import { Position } from 'vs/editor/common/core/position';
import { provideSuggestionItems, getSuggestionComparator, ISuggestionItem } from './suggest';
import { CompletionModel } from './completionModel';
@@ -19,22 +19,22 @@ import { CursorChangeReason, ICursorSelectionChangedEvent } from 'vs/editor/comm
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
export interface ICancelEvent {
retrigger: boolean;
readonly retrigger: boolean;
}
export interface ITriggerEvent {
auto: boolean;
readonly auto: boolean;
}
export interface ISuggestEvent {
completionModel: CompletionModel;
isFrozen: boolean;
auto: boolean;
readonly completionModel: CompletionModel;
readonly isFrozen: boolean;
readonly auto: boolean;
}
export interface SuggestTriggerContext {
auto: boolean;
triggerCharacter?: string;
readonly auto: boolean;
readonly triggerCharacter?: string;
}
export class LineContext {
@@ -59,25 +59,13 @@ export class LineContext {
return true;
}
static isInEditableRange(editor: ICodeEditor): boolean {
const model = editor.getModel();
const position = editor.getPosition();
if (model.hasEditableRange()) {
const editableRange = model.getEditableRange();
if (!editableRange.containsPosition(position)) {
return false;
}
}
return true;
}
readonly lineNumber: number;
readonly column: number;
readonly leadingLineContent: string;
readonly leadingWord: IWordAtPosition;
readonly auto: boolean;
constructor(model: IModel, position: Position, auto: boolean) {
constructor(model: ITextModel, position: Position, auto: boolean) {
this.leadingLineContent = model.getLineContent(position.lineNumber).substr(0, position.column - 1);
this.leadingWord = model.getWordUntilPosition(position);
this.lineNumber = position.lineNumber;
@@ -289,9 +277,9 @@ export class SuggestModel implements IDisposable {
this.cancel();
if (LineContext.shouldAutoTrigger(this._editor)) {
this._triggerAutoSuggestPromise = TPromise.timeout(this._quickSuggestDelay);
this._triggerAutoSuggestPromise.then(() => {
this._triggerAutoSuggestPromise = TPromise.timeout(this._quickSuggestDelay);
this._triggerAutoSuggestPromise.then(() => {
if (LineContext.shouldAutoTrigger(this._editor)) {
const model = this._editor.getModel();
const pos = this._editor.getPosition();
@@ -307,9 +295,8 @@ export class SuggestModel implements IDisposable {
} else {
// Check the type of the token that triggered this
model.tokenizeIfCheap(pos.lineNumber);
const { tokenType } = model
.getLineTokens(pos.lineNumber)
.findTokenAtOffset(Math.max(pos.column - 1 - 1, 0));
const lineTokens = model.getLineTokens(pos.lineNumber);
const tokenType = lineTokens.getStandardTokenType(lineTokens.findTokenIndexAtOffset(Math.max(pos.column - 1 - 1, 0)));
const inValidScope = quickSuggestions.other && tokenType === StandardTokenType.Other
|| quickSuggestions.comments && tokenType === StandardTokenType.Comment
|| quickSuggestions.strings && tokenType === StandardTokenType.String;
@@ -319,10 +306,10 @@ export class SuggestModel implements IDisposable {
}
}
this._triggerAutoSuggestPromise = null;
this.trigger({ auto: true });
});
}
}
this._triggerAutoSuggestPromise = null;
});
}
}
}
@@ -352,10 +339,6 @@ export class SuggestModel implements IDisposable {
const auto = context.auto;
const ctx = new LineContext(model, this._editor.getPosition(), auto);
if (!LineContext.isInEditableRange(this._editor)) {
return;
}
// Cancel previous requests, change state & update UI
this.cancel(retrigger);
this._state = auto ? State.Auto : State.Manual;
@@ -364,13 +347,23 @@ export class SuggestModel implements IDisposable {
// Capture context when request was sent
this._context = ctx;
// Build context for request
let suggestCtx: SuggestContext;
if (context.triggerCharacter) {
suggestCtx = {
triggerKind: SuggestTriggerKind.TriggerCharacter,
triggerCharacter: context.triggerCharacter
};
} else if (onlyFrom && onlyFrom.length) {
suggestCtx = { triggerKind: SuggestTriggerKind.TriggerForIncompleteCompletions };
} else {
suggestCtx = { triggerKind: SuggestTriggerKind.Invoke };
}
this._requestPromise = provideSuggestionItems(model, this._editor.getPosition(),
this._editor.getConfiguration().contribInfo.snippetSuggestions,
onlyFrom,
{
triggerCharacter: context.triggerCharacter,
triggerKind: context.triggerCharacter ? SuggestTriggerKind.TriggerCharacter : SuggestTriggerKind.Invoke
}
suggestCtx
).then(items => {
this._requestPromise = null;

View File

@@ -176,8 +176,14 @@ class Renderer implements IRenderer<ICompletionItem, ISuggestionTemplateData> {
}
disposeTemplate(templateData: ISuggestionTemplateData): void {
templateData.highlightedLabel.dispose();
templateData.disposables = dispose(templateData.disposables);
// {{SQL CARBON EDIT}}
if (templateData.highlightedLabel) {
templateData.highlightedLabel.dispose();
}
// {{SQL CARBON EDIT}}
if (templateData.disposables) {
templateData.disposables = dispose(templateData.disposables);
}
}
}
@@ -335,9 +341,15 @@ class SuggestionDetails {
}
}
export interface ISelectedSuggestion {
item: ICompletionItem;
index: number;
model: CompletionModel;
}
export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>, IDisposable {
private static ID: string = 'editor.widget.suggestWidget';
private static readonly ID: string = 'editor.widget.suggestWidget';
static LOADING_MESSAGE: string = nls.localize('suggestWidget.loading', "Loading...");
static NO_SUGGESTIONS_MESSAGE: string = nls.localize('suggestWidget.noSuggestions', "No suggestions.");
@@ -359,6 +371,7 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
private listElement: HTMLElement;
private details: SuggestionDetails;
private list: List<ICompletionItem>;
private listHeight: number;
private suggestWidgetVisible: IContextKey<boolean>;
private suggestWidgetMultipleSuggestions: IContextKey<boolean>;
@@ -368,14 +381,14 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
private showTimeout: TPromise<void>;
private toDispose: IDisposable[];
private onDidSelectEmitter = new Emitter<ICompletionItem>();
private onDidFocusEmitter = new Emitter<ICompletionItem>();
private onDidSelectEmitter = new Emitter<ISelectedSuggestion>();
private onDidFocusEmitter = new Emitter<ISelectedSuggestion>();
private onDidHideEmitter = new Emitter<this>();
private onDidShowEmitter = new Emitter<this>();
readonly onDidSelect: Event<ICompletionItem> = this.onDidSelectEmitter.event;
readonly onDidFocus: Event<ICompletionItem> = this.onDidFocusEmitter.event;
readonly onDidSelect: Event<ISelectedSuggestion> = this.onDidSelectEmitter.event;
readonly onDidFocus: Event<ISelectedSuggestion> = this.onDidFocusEmitter.event;
readonly onDidHide: Event<this> = this.onDidHideEmitter.event;
readonly onDidShow: Event<this> = this.onDidShowEmitter.event;
@@ -499,13 +512,12 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
}
const item = e.elements[0];
const index = e.indexes[0];
item.resolve().then(() => {
this.onDidSelectEmitter.fire(item);
this.onDidSelectEmitter.fire({ item, index, model: this.completionModel });
alert(nls.localize('suggestionAriaAccepted', "{0}, accepted", item.suggestion.label));
this.editor.focus();
});
alert(nls.localize('suggestionAriaAccepted', "{0}, accepted", item.suggestion.label));
this.editor.focus();
}
private _getSuggestionAriaAlertLabel(item: ICompletionItem): string {
@@ -591,7 +603,6 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
this.ignoreFocusEvents = false;
}
this.updateListHeight();
this.list.reveal(index);
this.currentSuggestionDetails = item.resolve()
@@ -613,7 +624,7 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
.then(() => this.currentSuggestionDetails = null);
// emit an event
this.onDidFocusEmitter.fire(item);
this.onDidFocusEmitter.fire({ item, index, model: this.completionModel });
}
private setState(state: State): void {
@@ -628,8 +639,7 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
switch (state) {
case State.Hidden:
hide(this.messageElement, this.details.element);
show(this.listElement);
hide(this.messageElement, this.details.element, this.listElement);
this.hide();
if (stateChanged) {
this.list.splice(0, this.list.length);
@@ -650,19 +660,12 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
this.show();
break;
case State.Open:
hide(this.messageElement);
hide(this.messageElement, this.details.element);
show(this.listElement);
if (this.expandDocsSettingFromStorage()
&& canExpandCompletionItem(this.list.getFocusedElements()[0])) {
show(this.details.element);
this.expandSideOrBelow();
} else {
hide(this.details.element);
}
this.show();
break;
case State.Frozen:
hide(this.messageElement, this.details.element);
hide(this.messageElement);
show(this.listElement);
this.show();
break;
@@ -673,10 +676,6 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
this._ariaAlert(this.details.getAriaLabel());
break;
}
if (stateChanged) {
this.editor.layoutContentWidget(this);
}
}
showTriggered(auto: boolean) {
@@ -738,8 +737,6 @@ 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([selectionIndex]);
this.list.reveal(selectionIndex, selectionIndex);
if (isFrozen) {
this.setState(State.Frozen);
@@ -747,6 +744,9 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
this.setState(State.Open);
}
this.list.reveal(selectionIndex, selectionIndex);
this.list.setFocus([selectionIndex]);
// Reset focus border
if (this.detailsBorderColor) {
this.details.element.style.borderColor = this.detailsBorderColor;
@@ -838,12 +838,16 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
}
}
getFocusedItem(): ICompletionItem {
getFocusedItem(): ISelectedSuggestion {
if (this.state !== State.Hidden
&& this.state !== State.Empty
&& this.state !== State.Loading) {
return this.list.getFocusedElements()[0];
return {
item: this.list.getFocusedElements()[0],
index: this.list.getFocus()[0],
model: this.completionModel
};
}
return undefined;
}
@@ -929,7 +933,12 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
}
private show(): void {
this.updateListHeight();
const newHeight = this.updateListHeight();
if (newHeight !== this.listHeight) {
this.editor.layoutContentWidget(this);
this.listHeight = newHeight;
}
this.suggestWidgetVisible.set(true);
this.showTimeout = TPromise.timeout(100).then(() => {
@@ -982,9 +991,6 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
this.element.style.lineHeight = `${this.unfocusedHeight}px`;
this.listElement.style.height = `${height}px`;
this.list.layout(height);
this.editor.layoutContentWidget(this);
return height;
}

View File

@@ -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 () {
export function createSuggestItem(label: string, overwriteBefore: number, type: SuggestionType = 'property', incomplete: boolean = false, position: IPosition = { lineNumber: 1, column: 1 }): ISuggestionItem {
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
};
suggestion: ISuggestion = {
label,
overwriteBefore,
insertText: label,
type
};
container: ISuggestResult = {
incomplete,
suggestions: [this.suggestion]
};
container: ISuggestResult = {
incomplete,
suggestions: [this.suggestion]
};
support: ISuggestSupport = {
provideCompletionItems(): any {
return;
}
};
resolve(): TPromise<void> {
return null;
support: ISuggestSupport = {
provideCompletionItems(): any {
return;
}
};
}
resolve(): TPromise<void> {
return null;
}
};
}
suite('CompletionModel', function () {
let model: CompletionModel;

View File

@@ -10,17 +10,17 @@ import { IDisposable } from 'vs/base/common/lifecycle';
import { SuggestRegistry } from 'vs/editor/common/modes';
import { provideSuggestionItems } from 'vs/editor/contrib/suggest/suggest';
import { Position } from 'vs/editor/common/core/position';
import { Model } from 'vs/editor/common/model/model';
import { TextModel } from 'vs/editor/common/model/textModel';
suite('Suggest', function () {
let model: Model;
let model: TextModel;
let registration: IDisposable;
setup(function () {
model = Model.createFromString('FOO\nbar\BAR\nfoo', undefined, undefined, URI.parse('foo:bar/path'));
model = TextModel.createFromString('FOO\nbar\BAR\nfoo', undefined, undefined, URI.parse('foo:bar/path'));
registration = SuggestRegistry.register({ pattern: 'bar/path', scheme: 'foo' }, {
provideCompletionItems() {
return {

View File

@@ -0,0 +1,86 @@
/*---------------------------------------------------------------------------------------------
* 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 * as assert from 'assert';
import { LRUMemory, 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 LRUMemory();
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
});
});

View File

@@ -8,8 +8,7 @@ import * as assert from 'assert';
import Event from 'vs/base/common/event';
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 { TextModel } from 'vs/editor/common/model/textModel';
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/suggestModel';
@@ -22,15 +21,19 @@ 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';
import { SuggestController } from 'vs/editor/contrib/suggest/suggestController';
import { IStorageService, NullStorageService } from 'vs/platform/storage/common/storage';
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
import { ISelectedSuggestion } from 'vs/editor/contrib/suggest/suggestWidget';
function createMockEditor(model: Model): TestCodeEditor {
function createMockEditor(model: TextModel): TestCodeEditor {
const contextKeyService = new MockContextKeyService();
const telemetryService = NullTelemetryService;
const instantiationService = new InstantiationService(new ServiceCollection(
[IContextKeyService, contextKeyService],
[ITelemetryService, telemetryService]
[ITelemetryService, telemetryService],
[IStorageService, NullStorageService]
));
const editor = new TestCodeEditor(new MockScopeLocation(), {}, instantiationService, contextKeyService);
@@ -40,10 +43,10 @@ function createMockEditor(model: Model): TestCodeEditor {
suite('SuggestModel - Context', function () {
let model: Model;
let model: TextModel;
setup(function () {
model = Model.createFromString('Das Pferd frisst keinen Gurkensalat - Philipp Reis 1861.\nWer hat\'s erfunden?');
model = TextModel.createFromString('Das Pferd frisst keinen Gurkensalat - Philipp Reis 1861.\nWer hat\'s erfunden?');
});
teardown(function () {
@@ -94,17 +97,17 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
};
let disposables: IDisposable[] = [];
let model: Model;
let model: TextModel;
setup(function () {
disposables = dispose(disposables);
model = Model.createFromString('abc def', undefined, undefined, URI.parse('test:somefile.ttt'));
model = TextModel.createFromString('abc def', undefined, undefined, URI.parse('test:somefile.ttt'));
disposables.push(model);
});
function withOracle(callback: (model: SuggestModel, editor: ICodeEditor) => any): TPromise<any> {
function withOracle(callback: (model: SuggestModel, editor: TestCodeEditor) => any): Promise<any> {
return new TPromise((resolve, reject) => {
return new Promise((resolve, reject) => {
const editor = createMockEditor(model);
const oracle = new SuggestModel(editor);
disposables.push(oracle, editor);
@@ -118,7 +121,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
}
function assertEvent<E>(event: Event<E>, action: () => any, assert: (e: E) => any) {
return new TPromise((resolve, reject) => {
return new Promise((resolve, reject) => {
const sub = event(e => {
sub.dispose();
try {
@@ -138,7 +141,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
test('events - cancel/trigger', function () {
return withOracle(model => {
return TPromise.join([
return Promise.all([
assertEvent(model.onDidCancel, function () {
model.cancel();
}, function (event) {
@@ -185,7 +188,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
disposables.push(SuggestRegistry.register({ scheme: 'test' }, alwaysEmptySupport));
return withOracle(model => {
return TPromise.join([
return Promise.all([
assertEvent(model.onDidCancel, function () {
model.trigger({ auto: true });
}, function (event) {
@@ -564,4 +567,53 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
});
});
});
test('Text changes for completion CodeAction are affected by the completion #39893', function () {
disposables.push(SuggestRegistry.register({ scheme: 'test' }, {
provideCompletionItems(doc, pos): ISuggestResult {
return {
incomplete: true,
suggestions: [{
label: 'bar',
type: 'property',
insertText: 'bar',
overwriteBefore: 2,
additionalTextEdits: [{
text: ', bar',
range: { startLineNumber: 1, endLineNumber: 1, startColumn: 17, endColumn: 17 }
}]
}]
};
}
}));
model.setValue('ba; import { foo } from "./b"');
return withOracle(async (sugget, editor) => {
class TestCtrl extends SuggestController {
_onDidSelectItem(item: ISelectedSuggestion) {
super._onDidSelectItem(item);
}
}
const ctrl = <TestCtrl>editor.registerAndInstantiateContribution(TestCtrl);
editor.registerAndInstantiateContribution(SnippetController2);
await assertEvent(sugget.onDidSuggest, () => {
editor.setPosition({ lineNumber: 1, column: 3 });
sugget.trigger({ auto: false });
}, event => {
assert.equal(event.completionModel.items.length, 1);
const [first] = event.completionModel.items;
assert.equal(first.suggestion.label, 'bar');
ctrl._onDidSelectItem({ item: first, index: 0, model: event.completionModel });
});
assert.equal(
model.getValue(),
'bar; import { foo, bar } from "./b"'
);
});
});
});