mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-21 12:20:29 -04:00
Merge VS Code 1.31.1 (#4283)
This commit is contained in:
@@ -3,21 +3,15 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { fuzzyScore, fuzzyScoreGracefulAggressive, anyScore, FuzzyScorer } from 'vs/base/common/filters';
|
||||
import { fuzzyScore, fuzzyScoreGracefulAggressive, anyScore, FuzzyScorer, FuzzyScore } from 'vs/base/common/filters';
|
||||
import { isDisposable } from 'vs/base/common/lifecycle';
|
||||
import { CompletionList, CompletionItemProvider, CompletionItemKind } from 'vs/editor/common/modes';
|
||||
import { ISuggestionItem, ensureLowerCaseVariants } from './suggest';
|
||||
import { CompletionItem } from './suggest';
|
||||
import { InternalSuggestOptions, EDITOR_DEFAULTS } from 'vs/editor/common/config/editorOptions';
|
||||
import { WordDistance } from 'vs/editor/contrib/suggest/wordDistance';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
|
||||
export interface ICompletionItem extends ISuggestionItem {
|
||||
matches?: number[];
|
||||
score?: number;
|
||||
idx?: number;
|
||||
distance?: number;
|
||||
word?: string;
|
||||
}
|
||||
type StrictCompletionItem = Required<CompletionItem>;
|
||||
|
||||
/* __GDPR__FRAGMENT__
|
||||
"ICompletionStats" : {
|
||||
@@ -47,7 +41,7 @@ const enum Refilter {
|
||||
|
||||
export class CompletionModel {
|
||||
|
||||
private readonly _items: ICompletionItem[];
|
||||
private readonly _items: CompletionItem[];
|
||||
private readonly _column: number;
|
||||
private readonly _wordDistance: WordDistance;
|
||||
private readonly _options: InternalSuggestOptions;
|
||||
@@ -55,12 +49,12 @@ export class CompletionModel {
|
||||
|
||||
private _lineContext: LineContext;
|
||||
private _refilterKind: Refilter;
|
||||
private _filteredItems: ICompletionItem[];
|
||||
private _filteredItems: StrictCompletionItem[];
|
||||
private _isIncomplete: Set<CompletionItemProvider>;
|
||||
private _stats: ICompletionStats;
|
||||
|
||||
constructor(
|
||||
items: ISuggestionItem[],
|
||||
items: CompletionItem[],
|
||||
column: number,
|
||||
lineContext: LineContext,
|
||||
wordDistance: WordDistance,
|
||||
@@ -105,7 +99,7 @@ export class CompletionModel {
|
||||
}
|
||||
}
|
||||
|
||||
get items(): ICompletionItem[] {
|
||||
get items(): CompletionItem[] {
|
||||
this._ensureCachedState();
|
||||
return this._filteredItems;
|
||||
}
|
||||
@@ -115,10 +109,10 @@ export class CompletionModel {
|
||||
return this._isIncomplete;
|
||||
}
|
||||
|
||||
adopt(except: Set<CompletionItemProvider>): ISuggestionItem[] {
|
||||
let res = new Array<ISuggestionItem>();
|
||||
adopt(except: Set<CompletionItemProvider>): CompletionItem[] {
|
||||
let res = new Array<CompletionItem>();
|
||||
for (let i = 0; i < this._items.length;) {
|
||||
if (!except.has(this._items[i].support)) {
|
||||
if (!except.has(this._items[i].provider)) {
|
||||
res.push(this._items[i]);
|
||||
|
||||
// unordered removed
|
||||
@@ -155,7 +149,7 @@ export class CompletionModel {
|
||||
|
||||
// incrementally filter less
|
||||
const source = this._refilterKind === Refilter.All ? this._items : this._filteredItems;
|
||||
const target: typeof source = [];
|
||||
const target: StrictCompletionItem[] = [];
|
||||
|
||||
// picks a score function based on the number of
|
||||
// items that we have to score/filter and based on the
|
||||
@@ -165,21 +159,17 @@ export class CompletionModel {
|
||||
for (let i = 0; i < source.length; i++) {
|
||||
|
||||
const item = source[i];
|
||||
const { suggestion, container } = item;
|
||||
|
||||
// make sure _labelLow, _filterTextLow, _sortTextLow exist
|
||||
ensureLowerCaseVariants(suggestion);
|
||||
|
||||
// collect those supports that signaled having
|
||||
// an incomplete result
|
||||
if (container.incomplete) {
|
||||
this._isIncomplete.add(item.support);
|
||||
if (item.container.incomplete) {
|
||||
this._isIncomplete.add(item.provider);
|
||||
}
|
||||
|
||||
// 'word' is that remainder of the current line that we
|
||||
// filter and score against. In theory each suggestion uses a
|
||||
// different word, but in practice not - that's why we cache
|
||||
const overwriteBefore = item.position.column - suggestion.range.startColumn;
|
||||
const overwriteBefore = item.position.column - item.completion.range.startColumn;
|
||||
const wordLen = overwriteBefore + characterCountDelta - (item.position.column - this._column);
|
||||
if (word.length !== wordLen) {
|
||||
word = wordLen === 0 ? '' : leadingLineContent.slice(-wordLen);
|
||||
@@ -196,8 +186,7 @@ export class CompletionModel {
|
||||
// the fallback-sort using the initial sort order.
|
||||
// use a score of `-100` because that is out of the
|
||||
// bound of values `fuzzyScore` will return
|
||||
item.score = -100;
|
||||
item.matches = undefined;
|
||||
item.score = FuzzyScore.Default;
|
||||
|
||||
} else {
|
||||
// skip word characters that are whitespace until
|
||||
@@ -215,40 +204,37 @@ export class CompletionModel {
|
||||
if (wordPos >= wordLen) {
|
||||
// the wordPos at which scoring starts is the whole word
|
||||
// and therefore the same rules as not having a word apply
|
||||
item.score = -100;
|
||||
item.matches = [];
|
||||
item.score = FuzzyScore.Default;
|
||||
|
||||
} else if (typeof suggestion.filterText === 'string') {
|
||||
} else if (typeof item.completion.filterText === 'string') {
|
||||
// when there is a `filterText` it must match the `word`.
|
||||
// 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 = scoreFn(word, wordLow, wordPos, suggestion.filterText, suggestion._filterTextLow, 0, false);
|
||||
let match = scoreFn(word, wordLow, wordPos, item.completion.filterText, item.filterTextLow!, 0, false);
|
||||
if (!match) {
|
||||
continue;
|
||||
continue; // NO match
|
||||
}
|
||||
item.score = match[0];
|
||||
item.matches = (fuzzyScore(word, wordLow, 0, suggestion.label, suggestion._labelLow, 0, true) || anyScore(word, suggestion.label))[1];
|
||||
item.score = anyScore(word, wordLow, 0, item.completion.label, item.labelLow, 0);
|
||||
item.score[0] = match[0]; // use score from filterText
|
||||
|
||||
} else {
|
||||
// by default match `word` against the `label`
|
||||
let match = scoreFn(word, wordLow, wordPos, suggestion.label, suggestion._labelLow, 0, false);
|
||||
if (match) {
|
||||
item.score = match[0];
|
||||
item.matches = match[1];
|
||||
} else {
|
||||
continue;
|
||||
let match = scoreFn(word, wordLow, wordPos, item.completion.label, item.labelLow, 0, false);
|
||||
if (!match) {
|
||||
continue; // NO match
|
||||
}
|
||||
item.score = match;
|
||||
}
|
||||
}
|
||||
|
||||
item.idx = i;
|
||||
item.distance = this._wordDistance.distance(item.position, suggestion);
|
||||
target.push(item);
|
||||
item.distance = this._wordDistance.distance(item.position, item.completion);
|
||||
target.push(item as StrictCompletionItem);
|
||||
|
||||
// update stats
|
||||
this._stats.suggestionCount++;
|
||||
switch (suggestion.kind) {
|
||||
switch (item.completion.kind) {
|
||||
case CompletionItemKind.Snippet: this._stats.snippetCount++; break;
|
||||
case CompletionItemKind.Text: this._stats.textCount++; break;
|
||||
}
|
||||
@@ -258,10 +244,10 @@ export class CompletionModel {
|
||||
this._refilterKind = Refilter.Nothing;
|
||||
}
|
||||
|
||||
private static _compareCompletionItems(a: ICompletionItem, b: ICompletionItem): number {
|
||||
if (a.score > b.score) {
|
||||
private static _compareCompletionItems(a: StrictCompletionItem, b: StrictCompletionItem): number {
|
||||
if (a.score[0] > b.score[0]) {
|
||||
return -1;
|
||||
} else if (a.score < b.score) {
|
||||
} else if (a.score[0] < b.score[0]) {
|
||||
return 1;
|
||||
} else if (a.distance < b.distance) {
|
||||
return -1;
|
||||
@@ -276,22 +262,22 @@ export class CompletionModel {
|
||||
}
|
||||
}
|
||||
|
||||
private static _compareCompletionItemsSnippetsDown(a: ICompletionItem, b: ICompletionItem): number {
|
||||
if (a.suggestion.kind !== b.suggestion.kind) {
|
||||
if (a.suggestion.kind === CompletionItemKind.Snippet) {
|
||||
private static _compareCompletionItemsSnippetsDown(a: StrictCompletionItem, b: StrictCompletionItem): number {
|
||||
if (a.completion.kind !== b.completion.kind) {
|
||||
if (a.completion.kind === CompletionItemKind.Snippet) {
|
||||
return 1;
|
||||
} else if (b.suggestion.kind === CompletionItemKind.Snippet) {
|
||||
} else if (b.completion.kind === CompletionItemKind.Snippet) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return CompletionModel._compareCompletionItems(a, b);
|
||||
}
|
||||
|
||||
private static _compareCompletionItemsSnippetsUp(a: ICompletionItem, b: ICompletionItem): number {
|
||||
if (a.suggestion.kind !== b.suggestion.kind) {
|
||||
if (a.suggestion.kind === CompletionItemKind.Snippet) {
|
||||
private static _compareCompletionItemsSnippetsUp(a: StrictCompletionItem, b: StrictCompletionItem): number {
|
||||
if (a.completion.kind !== b.completion.kind) {
|
||||
if (a.completion.kind === CompletionItemKind.Snippet) {
|
||||
return -1;
|
||||
} else if (b.suggestion.kind === CompletionItemKind.Snippet) {
|
||||
} else if (b.completion.kind === CompletionItemKind.Snippet) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,14 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
.monaco-editor .suggest-widget .monaco-list {
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: -moz-none;
|
||||
-ms-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/** Styles for each row in the list element **/
|
||||
|
||||
@@ -68,6 +75,8 @@
|
||||
background-repeat: no-repeat;
|
||||
background-position: 2px 2px;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents {
|
||||
@@ -173,7 +182,7 @@
|
||||
background-image: url('Misc_16x.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
background-size: 75%;
|
||||
}
|
||||
|
||||
.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.method::before,
|
||||
|
||||
@@ -10,12 +10,13 @@ import { onUnexpectedExternalError, canceled, isPromiseCanceledError } from 'vs/
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { CompletionList, CompletionItemProvider, CompletionItem, CompletionProviderRegistry, CompletionContext, CompletionTriggerKind, CompletionItemKind } from 'vs/editor/common/modes';
|
||||
import * as modes 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';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { FuzzyScore } from 'vs/base/common/filters';
|
||||
|
||||
export const Context = {
|
||||
Visible: new RawContextKey<boolean>('suggestWidgetVisible', false),
|
||||
@@ -24,23 +25,77 @@ export const Context = {
|
||||
AcceptSuggestionsOnEnter: new RawContextKey<boolean>('acceptSuggestionOnEnter', true)
|
||||
};
|
||||
|
||||
export interface ISuggestionItem {
|
||||
position: IPosition;
|
||||
suggestion: CompletionItem;
|
||||
container: CompletionList;
|
||||
support: CompletionItemProvider;
|
||||
resolve(token: CancellationToken): Thenable<void>;
|
||||
export class CompletionItem {
|
||||
|
||||
_brand: 'ISuggestionItem';
|
||||
|
||||
readonly resolve: (token: CancellationToken) => Promise<void>;
|
||||
|
||||
// perf
|
||||
readonly labelLow: string;
|
||||
readonly sortTextLow?: string;
|
||||
readonly filterTextLow?: string;
|
||||
|
||||
// sorting, filtering
|
||||
score: FuzzyScore = FuzzyScore.Default;
|
||||
distance: number = 0;
|
||||
idx?: number;
|
||||
word?: string;
|
||||
|
||||
constructor(
|
||||
readonly position: IPosition,
|
||||
readonly completion: modes.CompletionItem,
|
||||
readonly container: modes.CompletionList,
|
||||
readonly provider: modes.CompletionItemProvider,
|
||||
model: ITextModel
|
||||
) {
|
||||
// ensure lower-variants (perf)
|
||||
this.labelLow = completion.label.toLowerCase();
|
||||
this.sortTextLow = completion.sortText && completion.sortText.toLowerCase();
|
||||
this.filterTextLow = completion.filterText && completion.filterText.toLowerCase();
|
||||
|
||||
// create the suggestion resolver
|
||||
const { resolveCompletionItem } = provider;
|
||||
if (typeof resolveCompletionItem !== 'function') {
|
||||
this.resolve = () => Promise.resolve();
|
||||
} else {
|
||||
let cached: Promise<void> | undefined;
|
||||
this.resolve = (token) => {
|
||||
if (!cached) {
|
||||
let isDone = false;
|
||||
cached = Promise.resolve(resolveCompletionItem.call(provider, model, position, completion, token)).then(value => {
|
||||
assign(completion, value);
|
||||
isDone = true;
|
||||
}, err => {
|
||||
if (isPromiseCanceledError(err)) {
|
||||
// the IPC queue will reject the request with the
|
||||
// cancellation error -> reset cached
|
||||
cached = undefined;
|
||||
}
|
||||
});
|
||||
token.onCancellationRequested(() => {
|
||||
if (!isDone) {
|
||||
// cancellation after the request has been
|
||||
// dispatched -> reset cache
|
||||
cached = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
return cached;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type SnippetConfig = 'top' | 'bottom' | 'inline' | 'none';
|
||||
|
||||
let _snippetSuggestSupport: CompletionItemProvider;
|
||||
let _snippetSuggestSupport: modes.CompletionItemProvider;
|
||||
|
||||
export function getSnippetSuggestSupport(): CompletionItemProvider {
|
||||
export function getSnippetSuggestSupport(): modes.CompletionItemProvider {
|
||||
return _snippetSuggestSupport;
|
||||
}
|
||||
|
||||
export function setSnippetSuggestSupport(support: CompletionItemProvider): CompletionItemProvider {
|
||||
export function setSnippetSuggestSupport(support: modes.CompletionItemProvider): modes.CompletionItemProvider {
|
||||
const old = _snippetSuggestSupport;
|
||||
_snippetSuggestSupport = support;
|
||||
return old;
|
||||
@@ -50,12 +105,12 @@ export function provideSuggestionItems(
|
||||
model: ITextModel,
|
||||
position: Position,
|
||||
snippetConfig: SnippetConfig = 'bottom',
|
||||
onlyFrom?: CompletionItemProvider[],
|
||||
context?: CompletionContext,
|
||||
onlyFrom?: modes.CompletionItemProvider[],
|
||||
context?: modes.CompletionContext,
|
||||
token: CancellationToken = CancellationToken.None
|
||||
): Promise<ISuggestionItem[]> {
|
||||
): Promise<CompletionItem[]> {
|
||||
|
||||
const allSuggestions: ISuggestionItem[] = [];
|
||||
const allSuggestions: CompletionItem[] = [];
|
||||
const acceptSuggestion = createSuggesionFilter(snippetConfig);
|
||||
|
||||
const wordUntil = model.getWordUntilPosition(position);
|
||||
@@ -64,27 +119,27 @@ export function provideSuggestionItems(
|
||||
position = position.clone();
|
||||
|
||||
// get provider groups, always add snippet suggestion provider
|
||||
const supports = CompletionProviderRegistry.orderedGroups(model);
|
||||
const supports = modes.CompletionProviderRegistry.orderedGroups(model);
|
||||
|
||||
// add snippets provider unless turned off
|
||||
if (snippetConfig !== 'none' && _snippetSuggestSupport) {
|
||||
supports.unshift([_snippetSuggestSupport]);
|
||||
}
|
||||
|
||||
const suggestConext = context || { triggerKind: CompletionTriggerKind.Invoke };
|
||||
const suggestConext = context || { triggerKind: modes.CompletionTriggerKind.Invoke };
|
||||
|
||||
// add suggestions from contributed providers - providers are ordered in groups of
|
||||
// equal score and once a group produces a result the process stops
|
||||
let hasResult = false;
|
||||
const factory = supports.map(supports => () => {
|
||||
// for each support in the group ask for suggestions
|
||||
return Promise.all(supports.map(support => {
|
||||
return Promise.all(supports.map(provider => {
|
||||
|
||||
if (isNonEmptyArray(onlyFrom) && onlyFrom.indexOf(support) < 0) {
|
||||
if (isNonEmptyArray(onlyFrom) && onlyFrom.indexOf(provider) < 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return Promise.resolve(support.provideCompletionItems(model, position, suggestConext, token)).then(container => {
|
||||
return Promise.resolve(provider.provideCompletionItems(model, position, suggestConext, token)).then(container => {
|
||||
|
||||
const len = allSuggestions.length;
|
||||
|
||||
@@ -97,21 +152,12 @@ export function provideSuggestionItems(
|
||||
suggestion.range = defaultRange;
|
||||
}
|
||||
|
||||
// fill in lower-case text
|
||||
ensureLowerCaseVariants(suggestion);
|
||||
|
||||
allSuggestions.push({
|
||||
position,
|
||||
container,
|
||||
suggestion,
|
||||
support,
|
||||
resolve: createSuggestionResolver(support, suggestion, model, position)
|
||||
});
|
||||
allSuggestions.push(new CompletionItem(position, suggestion, container, provider, model));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (len !== allSuggestions.length && support !== _snippetSuggestSupport) {
|
||||
if (len !== allSuggestions.length && provider !== _snippetSuggestSupport) {
|
||||
hasResult = true;
|
||||
}
|
||||
|
||||
@@ -139,101 +185,55 @@ export function provideSuggestionItems(
|
||||
return result;
|
||||
}
|
||||
|
||||
export function ensureLowerCaseVariants(suggestion: CompletionItem) {
|
||||
if (!suggestion._labelLow) {
|
||||
suggestion._labelLow = suggestion.label.toLowerCase();
|
||||
}
|
||||
if (suggestion.sortText && !suggestion._sortTextLow) {
|
||||
suggestion._sortTextLow = suggestion.sortText.toLowerCase();
|
||||
}
|
||||
if (suggestion.filterText && !suggestion._filterTextLow) {
|
||||
suggestion._filterTextLow = suggestion.filterText.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
function createSuggestionResolver(provider: CompletionItemProvider, suggestion: CompletionItem, model: ITextModel, position: Position): (token: CancellationToken) => Promise<void> {
|
||||
|
||||
const { resolveCompletionItem } = provider;
|
||||
|
||||
if (typeof resolveCompletionItem !== 'function') {
|
||||
return () => Promise.resolve();
|
||||
}
|
||||
|
||||
let cached: Promise<void> | undefined;
|
||||
return (token) => {
|
||||
if (!cached) {
|
||||
let isDone = false;
|
||||
cached = Promise.resolve(provider.resolveCompletionItem!(model, position, suggestion, token)).then(value => {
|
||||
assign(suggestion, value);
|
||||
isDone = true;
|
||||
}, err => {
|
||||
if (isPromiseCanceledError(err)) {
|
||||
// the IPC queue will reject the request with the
|
||||
// cancellation error -> reset cached
|
||||
cached = undefined;
|
||||
}
|
||||
});
|
||||
token.onCancellationRequested(() => {
|
||||
if (!isDone) {
|
||||
// cancellation after the request has been
|
||||
// dispatched -> reset cache
|
||||
cached = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
return cached;
|
||||
};
|
||||
}
|
||||
|
||||
function createSuggesionFilter(snippetConfig: SnippetConfig): (candidate: CompletionItem) => boolean {
|
||||
function createSuggesionFilter(snippetConfig: SnippetConfig): (candidate: modes.CompletionItem) => boolean {
|
||||
if (snippetConfig === 'none') {
|
||||
return suggestion => suggestion.kind !== CompletionItemKind.Snippet;
|
||||
return suggestion => suggestion.kind !== modes.CompletionItemKind.Snippet;
|
||||
} else {
|
||||
return () => true;
|
||||
}
|
||||
}
|
||||
function defaultComparator(a: ISuggestionItem, b: ISuggestionItem): number {
|
||||
function defaultComparator(a: CompletionItem, b: CompletionItem): number {
|
||||
// check with 'sortText'
|
||||
if (a.suggestion._sortTextLow && b.suggestion._sortTextLow) {
|
||||
if (a.suggestion._sortTextLow < b.suggestion._sortTextLow) {
|
||||
if (a.sortTextLow && b.sortTextLow) {
|
||||
if (a.sortTextLow < b.sortTextLow) {
|
||||
return -1;
|
||||
} else if (a.suggestion._sortTextLow > b.suggestion._sortTextLow) {
|
||||
} else if (a.sortTextLow > b.sortTextLow) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// check with 'label'
|
||||
if (a.suggestion.label < b.suggestion.label) {
|
||||
if (a.completion.label < b.completion.label) {
|
||||
return -1;
|
||||
} else if (a.suggestion.label > b.suggestion.label) {
|
||||
} else if (a.completion.label > b.completion.label) {
|
||||
return 1;
|
||||
}
|
||||
// check with 'type'
|
||||
return a.suggestion.kind - b.suggestion.kind;
|
||||
return a.completion.kind - b.completion.kind;
|
||||
}
|
||||
|
||||
function snippetUpComparator(a: ISuggestionItem, b: ISuggestionItem): number {
|
||||
if (a.suggestion.kind !== b.suggestion.kind) {
|
||||
if (a.suggestion.kind === CompletionItemKind.Snippet) {
|
||||
function snippetUpComparator(a: CompletionItem, b: CompletionItem): number {
|
||||
if (a.completion.kind !== b.completion.kind) {
|
||||
if (a.completion.kind === modes.CompletionItemKind.Snippet) {
|
||||
return -1;
|
||||
} else if (b.suggestion.kind === CompletionItemKind.Snippet) {
|
||||
} else if (b.completion.kind === modes.CompletionItemKind.Snippet) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return defaultComparator(a, b);
|
||||
}
|
||||
|
||||
function snippetDownComparator(a: ISuggestionItem, b: ISuggestionItem): number {
|
||||
if (a.suggestion.kind !== b.suggestion.kind) {
|
||||
if (a.suggestion.kind === CompletionItemKind.Snippet) {
|
||||
function snippetDownComparator(a: CompletionItem, b: CompletionItem): number {
|
||||
if (a.completion.kind !== b.completion.kind) {
|
||||
if (a.completion.kind === modes.CompletionItemKind.Snippet) {
|
||||
return 1;
|
||||
} else if (b.suggestion.kind === CompletionItemKind.Snippet) {
|
||||
} else if (b.completion.kind === modes.CompletionItemKind.Snippet) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return defaultComparator(a, b);
|
||||
}
|
||||
|
||||
export function getSuggestionComparator(snippetConfig: SnippetConfig): (a: ISuggestionItem, b: ISuggestionItem) => number {
|
||||
export function getSuggestionComparator(snippetConfig: SnippetConfig): (a: CompletionItem, b: CompletionItem) => number {
|
||||
if (snippetConfig === 'top') {
|
||||
return snippetUpComparator;
|
||||
} else if (snippetConfig === 'bottom') {
|
||||
@@ -245,12 +245,12 @@ export function getSuggestionComparator(snippetConfig: SnippetConfig): (a: ISugg
|
||||
|
||||
registerDefaultLanguageCommand('_executeCompletionItemProvider', (model, position, args) => {
|
||||
|
||||
const result: CompletionList = {
|
||||
const result: modes.CompletionList = {
|
||||
incomplete: false,
|
||||
suggestions: []
|
||||
};
|
||||
|
||||
let resolving: Thenable<any>[] = [];
|
||||
let resolving: Promise<any>[] = [];
|
||||
let maxItemsToResolve = args['maxItemsToResolve'] || 0;
|
||||
|
||||
return provideSuggestionItems(model, position).then(items => {
|
||||
@@ -259,7 +259,7 @@ registerDefaultLanguageCommand('_executeCompletionItemProvider', (model, positio
|
||||
resolving.push(item.resolve(CancellationToken.None));
|
||||
}
|
||||
result.incomplete = result.incomplete || item.container.incomplete;
|
||||
result.suggestions.push(item.suggestion);
|
||||
result.suggestions.push(item.completion);
|
||||
}
|
||||
}).then(() => {
|
||||
return Promise.all(resolving);
|
||||
@@ -269,15 +269,15 @@ registerDefaultLanguageCommand('_executeCompletionItemProvider', (model, positio
|
||||
});
|
||||
|
||||
interface SuggestController extends IEditorContribution {
|
||||
triggerSuggest(onlyFrom?: CompletionItemProvider[]): void;
|
||||
triggerSuggest(onlyFrom?: modes.CompletionItemProvider[]): void;
|
||||
}
|
||||
|
||||
|
||||
let _provider = new class implements CompletionItemProvider {
|
||||
let _provider = new class implements modes.CompletionItemProvider {
|
||||
|
||||
onlyOnceSuggestions: CompletionItem[] = [];
|
||||
onlyOnceSuggestions: modes.CompletionItem[] = [];
|
||||
|
||||
provideCompletionItems(): CompletionList {
|
||||
provideCompletionItems(): modes.CompletionList {
|
||||
let suggestions = this.onlyOnceSuggestions.slice(0);
|
||||
let result = { suggestions };
|
||||
this.onlyOnceSuggestions.length = 0;
|
||||
@@ -285,9 +285,9 @@ let _provider = new class implements CompletionItemProvider {
|
||||
}
|
||||
};
|
||||
|
||||
CompletionProviderRegistry.register('*', _provider);
|
||||
modes.CompletionProviderRegistry.register('*', _provider);
|
||||
|
||||
export function showSimpleSuggestions(editor: ICodeEditor, suggestions: CompletionItem[]) {
|
||||
export function showSimpleSuggestions(editor: ICodeEditor, suggestions: modes.CompletionItem[]) {
|
||||
setTimeout(() => {
|
||||
_provider.onlyOnceSuggestions.push(...suggestions);
|
||||
editor.getContribution<SuggestController>('editor.contrib.suggestController').triggerSuggest([_provider]);
|
||||
|
||||
@@ -16,8 +16,8 @@ export class SuggestAlternatives {
|
||||
private readonly _ckOtherSuggestions: IContextKey<boolean>;
|
||||
|
||||
private _index: number;
|
||||
private _model: CompletionModel;
|
||||
private _acceptNext: (selected: ISelectedSuggestion) => any;
|
||||
private _model: CompletionModel | undefined;
|
||||
private _acceptNext: ((selected: ISelectedSuggestion) => any) | undefined;
|
||||
private _listener: IDisposable;
|
||||
private _ignore: boolean;
|
||||
|
||||
@@ -73,7 +73,7 @@ export class SuggestAlternatives {
|
||||
if (newIndex === index) {
|
||||
break;
|
||||
}
|
||||
if (!model.items[newIndex].suggestion.additionalTextEdits) {
|
||||
if (!model.items[newIndex].completion.additionalTextEdits) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ export class SuggestAlternatives {
|
||||
try {
|
||||
this._ignore = true;
|
||||
this._index = SuggestAlternatives._moveIndex(fwd, this._model, this._index);
|
||||
this._acceptNext({ index: this._index, item: this._model.items[this._index], model: this._model });
|
||||
this._acceptNext!({ index: this._index, item: this._model.items[this._index], model: this._model });
|
||||
} finally {
|
||||
this._ignore = false;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
@@ -12,33 +12,36 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorAction, EditorCommand, registerEditorAction, registerEditorCommand, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IEditorContribution, ScrollType, Handler } from 'vs/editor/common/editorCommon';
|
||||
import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { CompletionItemProvider, CompletionItemInsertTextRule } from 'vs/editor/common/modes';
|
||||
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
|
||||
import { SnippetParser } from 'vs/editor/contrib/snippet/snippetParser';
|
||||
import { SuggestMemories } from 'vs/editor/contrib/suggest/suggestMemory';
|
||||
import { ISuggestMemoryService } from 'vs/editor/contrib/suggest/suggestMemory';
|
||||
import * as nls from 'vs/nls';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { ICompletionItem } from './completionModel';
|
||||
import { Context as SuggestContext, ISuggestionItem } from './suggest';
|
||||
import { Context as SuggestContext, CompletionItem } from './suggest';
|
||||
import { SuggestAlternatives } from './suggestAlternatives';
|
||||
import { State, SuggestModel } from './suggestModel';
|
||||
import { ISelectedSuggestion, SuggestWidget } from './suggestWidget';
|
||||
import { WordContextKey } from 'vs/editor/contrib/suggest/wordContextKey';
|
||||
import { once, anyEvent } from 'vs/base/common/event';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
|
||||
import { IdleValue } from 'vs/base/common/async';
|
||||
import { CharacterSet } from 'vs/editor/common/core/characterClassifier';
|
||||
import { isObject } from 'vs/base/common/types';
|
||||
|
||||
class AcceptOnCharacterOracle {
|
||||
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
private _activeAcceptCharacters = new Set<string>();
|
||||
private _activeItem: ISelectedSuggestion;
|
||||
private _active?: {
|
||||
readonly acceptCharacters: CharacterSet;
|
||||
readonly item: ISelectedSuggestion;
|
||||
};
|
||||
|
||||
constructor(editor: ICodeEditor, widget: SuggestWidget, accept: (selected: ISelectedSuggestion) => any) {
|
||||
|
||||
@@ -47,31 +50,32 @@ class AcceptOnCharacterOracle {
|
||||
this._disposables.push(widget.onDidHide(this.reset, this));
|
||||
|
||||
this._disposables.push(editor.onWillType(text => {
|
||||
if (this._activeItem) {
|
||||
const ch = text[text.length - 1];
|
||||
if (this._activeAcceptCharacters.has(ch) && editor.getConfiguration().contribInfo.acceptSuggestionOnCommitCharacter) {
|
||||
accept(this._activeItem);
|
||||
if (this._active) {
|
||||
const ch = text.charCodeAt(text.length - 1);
|
||||
if (this._active.acceptCharacters.has(ch) && editor.getConfiguration().contribInfo.acceptSuggestionOnCommitCharacter) {
|
||||
accept(this._active.item);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private _onItem(selected: ISelectedSuggestion): void {
|
||||
if (!selected || isFalsyOrEmpty(selected.item.suggestion.commitCharacters)) {
|
||||
private _onItem(selected: ISelectedSuggestion | undefined): void {
|
||||
if (!selected || !isNonEmptyArray(selected.item.completion.commitCharacters)) {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
this._activeItem = selected;
|
||||
this._activeAcceptCharacters.clear();
|
||||
for (const ch of selected.item.suggestion.commitCharacters) {
|
||||
|
||||
const acceptCharacters = new CharacterSet();
|
||||
for (const ch of selected.item.completion.commitCharacters) {
|
||||
if (ch.length > 0) {
|
||||
this._activeAcceptCharacters.add(ch[0]);
|
||||
acceptCharacters.add(ch.charCodeAt(0));
|
||||
}
|
||||
}
|
||||
this._active = { acceptCharacters, item: selected };
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this._activeItem = undefined;
|
||||
this._active = undefined;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
@@ -87,9 +91,8 @@ export class SuggestController implements IEditorContribution {
|
||||
return editor.getContribution<SuggestController>(SuggestController.ID);
|
||||
}
|
||||
|
||||
private _model: SuggestModel;
|
||||
private _widget: SuggestWidget;
|
||||
private readonly _memory: IdleValue<SuggestMemories>;
|
||||
private readonly _model: SuggestModel;
|
||||
private readonly _widget: IdleValue<SuggestWidget>;
|
||||
private readonly _alternatives: IdleValue<SuggestAlternatives>;
|
||||
private _toDispose: IDisposable[] = [];
|
||||
|
||||
@@ -98,15 +101,62 @@ export class SuggestController implements IEditorContribution {
|
||||
constructor(
|
||||
private _editor: ICodeEditor,
|
||||
@IEditorWorkerService editorWorker: IEditorWorkerService,
|
||||
@ISuggestMemoryService private readonly _memoryService: ISuggestMemoryService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
) {
|
||||
this._model = new SuggestModel(this._editor, editorWorker);
|
||||
this._memory = new IdleValue(() => {
|
||||
let res = _instantiationService.createInstance(SuggestMemories, this._editor);
|
||||
this._toDispose.push(res);
|
||||
return res;
|
||||
|
||||
this._widget = new IdleValue(() => {
|
||||
|
||||
const widget = this._instantiationService.createInstance(SuggestWidget, this._editor);
|
||||
|
||||
this._toDispose.push(widget);
|
||||
this._toDispose.push(widget.onDidSelect(item => this._onDidSelectItem(item, false, true), this));
|
||||
|
||||
// Wire up logic to accept a suggestion on certain characters
|
||||
const autoAcceptOracle = new AcceptOnCharacterOracle(this._editor, widget, item => this._onDidSelectItem(item, false, true));
|
||||
this._toDispose.push(
|
||||
autoAcceptOracle,
|
||||
this._model.onDidSuggest(e => {
|
||||
if (e.completionModel.items.length === 0) {
|
||||
autoAcceptOracle.reset();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Wire up makes text edit context key
|
||||
let makesTextEdit = SuggestContext.MakesTextEdit.bindTo(this._contextKeyService);
|
||||
this._toDispose.push(widget.onDidFocus(({ item }) => {
|
||||
|
||||
const position = this._editor.getPosition()!;
|
||||
const startColumn = item.completion.range.startColumn;
|
||||
const endColumn = position.column;
|
||||
let value = true;
|
||||
if (
|
||||
this._editor.getConfiguration().contribInfo.acceptSuggestionOnEnter === 'smart'
|
||||
&& this._model.state === State.Auto
|
||||
&& !item.completion.command
|
||||
&& !item.completion.additionalTextEdits
|
||||
&& !(item.completion.insertTextRules! & CompletionItemInsertTextRule.InsertAsSnippet)
|
||||
&& endColumn - startColumn === item.completion.insertText.length
|
||||
) {
|
||||
const oldText = this._editor.getModel()!.getValueInRange({
|
||||
startLineNumber: position.lineNumber,
|
||||
startColumn,
|
||||
endLineNumber: position.lineNumber,
|
||||
endColumn
|
||||
});
|
||||
value = oldText !== item.completion.insertText;
|
||||
}
|
||||
makesTextEdit.set(value);
|
||||
}));
|
||||
this._toDispose.push({
|
||||
dispose() { makesTextEdit.reset(); }
|
||||
});
|
||||
|
||||
return widget;
|
||||
});
|
||||
|
||||
this._alternatives = new IdleValue(() => {
|
||||
@@ -118,20 +168,17 @@ export class SuggestController implements IEditorContribution {
|
||||
this._toDispose.push(_instantiationService.createInstance(WordContextKey, _editor));
|
||||
|
||||
this._toDispose.push(this._model.onDidTrigger(e => {
|
||||
if (!this._widget) {
|
||||
this._createSuggestWidget();
|
||||
}
|
||||
this._widget.showTriggered(e.auto, e.shy ? 250 : 50);
|
||||
this._widget.getValue().showTriggered(e.auto, e.shy ? 250 : 50);
|
||||
}));
|
||||
this._toDispose.push(this._model.onDidSuggest(e => {
|
||||
if (!e.shy) {
|
||||
let index = this._memory.getValue().select(this._editor.getModel(), this._editor.getPosition(), e.completionModel.items);
|
||||
this._widget.showSuggestions(e.completionModel, index, e.isFrozen, e.auto);
|
||||
let index = this._memoryService.select(this._editor.getModel()!, this._editor.getPosition()!, e.completionModel.items);
|
||||
this._widget.getValue().showSuggestions(e.completionModel, index, e.isFrozen, e.auto);
|
||||
}
|
||||
}));
|
||||
this._toDispose.push(this._model.onDidCancel(e => {
|
||||
if (this._widget && !e.retrigger) {
|
||||
this._widget.hideWidget();
|
||||
this._widget.getValue().hideWidget();
|
||||
}
|
||||
}));
|
||||
this._toDispose.push(this._editor.onDidBlurEditorText(() => {
|
||||
@@ -150,51 +197,6 @@ export class SuggestController implements IEditorContribution {
|
||||
updateFromConfig();
|
||||
}
|
||||
|
||||
private _createSuggestWidget(): void {
|
||||
|
||||
this._widget = this._instantiationService.createInstance(SuggestWidget, this._editor);
|
||||
this._toDispose.push(this._widget.onDidSelect(item => this._onDidSelectItem(item, false, true), this));
|
||||
|
||||
// Wire up logic to accept a suggestion on certain characters
|
||||
const autoAcceptOracle = new AcceptOnCharacterOracle(this._editor, this._widget, item => this._onDidSelectItem(item, false, true));
|
||||
this._toDispose.push(
|
||||
autoAcceptOracle,
|
||||
this._model.onDidSuggest(e => {
|
||||
if (e.completionModel.items.length === 0) {
|
||||
autoAcceptOracle.reset();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
let makesTextEdit = SuggestContext.MakesTextEdit.bindTo(this._contextKeyService);
|
||||
this._toDispose.push(this._widget.onDidFocus(({ item }) => {
|
||||
|
||||
const position = this._editor.getPosition();
|
||||
const startColumn = item.suggestion.range.startColumn;
|
||||
const endColumn = position.column;
|
||||
let value = true;
|
||||
if (
|
||||
this._editor.getConfiguration().contribInfo.acceptSuggestionOnEnter === 'smart'
|
||||
&& this._model.state === State.Auto
|
||||
&& !item.suggestion.command
|
||||
&& !item.suggestion.additionalTextEdits
|
||||
&& !(item.suggestion.insertTextRules & CompletionItemInsertTextRule.InsertAsSnippet)
|
||||
&& endColumn - startColumn === item.suggestion.insertText.length
|
||||
) {
|
||||
const oldText = this._editor.getModel().getValueInRange({
|
||||
startLineNumber: position.lineNumber,
|
||||
startColumn,
|
||||
endLineNumber: position.lineNumber,
|
||||
endColumn
|
||||
});
|
||||
value = oldText !== item.suggestion.insertText;
|
||||
}
|
||||
makesTextEdit.set(value);
|
||||
}));
|
||||
this._toDispose.push({
|
||||
dispose() { makesTextEdit.reset(); }
|
||||
});
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return SuggestController.ID;
|
||||
@@ -202,26 +204,25 @@ export class SuggestController implements IEditorContribution {
|
||||
|
||||
dispose(): void {
|
||||
this._toDispose = dispose(this._toDispose);
|
||||
if (this._widget) {
|
||||
this._widget.dispose();
|
||||
this._widget = null;
|
||||
}
|
||||
this._widget.dispose();
|
||||
if (this._model) {
|
||||
this._model.dispose();
|
||||
this._model = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected _onDidSelectItem(event: ISelectedSuggestion, keepAlternativeSuggestions: boolean, undoStops: boolean): void {
|
||||
protected _onDidSelectItem(event: ISelectedSuggestion | undefined, keepAlternativeSuggestions: boolean, undoStops: boolean): void {
|
||||
if (!event || !event.item) {
|
||||
this._alternatives.getValue().reset();
|
||||
this._model.cancel();
|
||||
return;
|
||||
}
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = this._editor.getModel();
|
||||
const modelVersionNow = model.getAlternativeVersionId();
|
||||
const { suggestion, position } = event.item;
|
||||
const { completion: suggestion, position } = event.item;
|
||||
const editorColumn = this._editor.getPosition().column;
|
||||
const columnDelta = editorColumn - position.column;
|
||||
|
||||
@@ -236,10 +237,10 @@ export class SuggestController implements IEditorContribution {
|
||||
}
|
||||
|
||||
// keep item in memory
|
||||
this._memory.getValue().memorize(model, this._editor.getPosition(), event.item);
|
||||
this._memoryService.memorize(model, this._editor.getPosition(), event.item);
|
||||
|
||||
let { insertText } = suggestion;
|
||||
if (!(suggestion.insertTextRules & CompletionItemInsertTextRule.InsertAsSnippet)) {
|
||||
if (!(suggestion.insertTextRules! & CompletionItemInsertTextRule.InsertAsSnippet)) {
|
||||
insertText = SnippetParser.escape(insertText);
|
||||
}
|
||||
|
||||
@@ -251,7 +252,7 @@ export class SuggestController implements IEditorContribution {
|
||||
overwriteBefore + columnDelta,
|
||||
overwriteAfter,
|
||||
false, false,
|
||||
!(suggestion.insertTextRules & CompletionItemInsertTextRule.KeepWhitespace)
|
||||
!(suggestion.insertTextRules! & CompletionItemInsertTextRule.KeepWhitespace)
|
||||
);
|
||||
|
||||
if (undoStops) {
|
||||
@@ -264,11 +265,11 @@ export class SuggestController implements IEditorContribution {
|
||||
|
||||
} else if (suggestion.command.id === TriggerSuggestAction.id) {
|
||||
// retigger
|
||||
this._model.trigger({ auto: true }, true);
|
||||
this._model.trigger({ auto: true, shy: false }, true);
|
||||
|
||||
} else {
|
||||
// exec command, done
|
||||
this._commandService.executeCommand(suggestion.command.id, ...suggestion.command.arguments).then(undefined, onUnexpectedError);
|
||||
this._commandService.executeCommand(suggestion.command.id, ...(suggestion.command.arguments ? [...suggestion.command.arguments] : [])).catch(onUnexpectedError);
|
||||
this._model.cancel();
|
||||
}
|
||||
|
||||
@@ -290,54 +291,59 @@ export class SuggestController implements IEditorContribution {
|
||||
this._alertCompletionItem(event.item);
|
||||
}
|
||||
|
||||
private _alertCompletionItem({ suggestion }: ICompletionItem): void {
|
||||
private _alertCompletionItem({ completion: suggestion }: CompletionItem): void {
|
||||
let msg = nls.localize('arai.alert.snippet', "Accepting '{0}' did insert the following text: {1}", suggestion.label, suggestion.insertText);
|
||||
alert(msg);
|
||||
}
|
||||
|
||||
triggerSuggest(onlyFrom?: CompletionItemProvider[]): void {
|
||||
this._model.trigger({ auto: false }, false, onlyFrom);
|
||||
this._editor.revealLine(this._editor.getPosition().lineNumber, ScrollType.Smooth);
|
||||
this._editor.focus();
|
||||
if (this._editor.hasModel()) {
|
||||
this._model.trigger({ auto: false, shy: false }, false, onlyFrom);
|
||||
this._editor.revealLine(this._editor.getPosition().lineNumber, ScrollType.Smooth);
|
||||
this._editor.focus();
|
||||
}
|
||||
}
|
||||
|
||||
triggerSuggestAndAcceptBest(defaultTypeText: string): void {
|
||||
triggerSuggestAndAcceptBest(arg: { fallback: string }): void {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
|
||||
}
|
||||
const positionNow = this._editor.getPosition();
|
||||
|
||||
const fallback = () => {
|
||||
if (positionNow.equals(this._editor.getPosition())) {
|
||||
this._editor.trigger('suggest', Handler.Type, { text: defaultTypeText });
|
||||
if (positionNow.equals(this._editor.getPosition()!)) {
|
||||
this._commandService.executeCommand(arg.fallback);
|
||||
}
|
||||
};
|
||||
|
||||
const makesTextEdit = (item: ISuggestionItem): boolean => {
|
||||
if (item.suggestion.insertTextRules & CompletionItemInsertTextRule.InsertAsSnippet || item.suggestion.additionalTextEdits) {
|
||||
const makesTextEdit = (item: CompletionItem): boolean => {
|
||||
if (item.completion.insertTextRules! & CompletionItemInsertTextRule.InsertAsSnippet || item.completion.additionalTextEdits) {
|
||||
// snippet, other editor -> makes edit
|
||||
return true;
|
||||
}
|
||||
const position = this._editor.getPosition();
|
||||
const startColumn = item.suggestion.range.startColumn;
|
||||
const position = this._editor.getPosition()!;
|
||||
const startColumn = item.completion.range.startColumn;
|
||||
const endColumn = position.column;
|
||||
if (endColumn - startColumn !== item.suggestion.insertText.length) {
|
||||
if (endColumn - startColumn !== item.completion.insertText.length) {
|
||||
// unequal lengths -> makes edit
|
||||
return true;
|
||||
}
|
||||
const textNow = this._editor.getModel().getValueInRange({
|
||||
const textNow = this._editor.getModel()!.getValueInRange({
|
||||
startLineNumber: position.lineNumber,
|
||||
startColumn,
|
||||
endLineNumber: position.lineNumber,
|
||||
endColumn
|
||||
});
|
||||
// unequal text -> makes edit
|
||||
return textNow !== item.suggestion.insertText;
|
||||
return textNow !== item.completion.insertText;
|
||||
};
|
||||
|
||||
once(this._model.onDidTrigger)(_ => {
|
||||
Event.once(this._model.onDidTrigger)(_ => {
|
||||
// wait for trigger because only then the cancel-event is trustworthy
|
||||
let listener: IDisposable[] = [];
|
||||
|
||||
anyEvent<any>(this._model.onDidTrigger, this._model.onDidCancel)(() => {
|
||||
Event.any<any>(this._model.onDidTrigger, this._model.onDidCancel)(() => {
|
||||
// retrigger or cancel -> try to type default text
|
||||
dispose(listener);
|
||||
fallback();
|
||||
@@ -349,7 +355,7 @@ export class SuggestController implements IEditorContribution {
|
||||
fallback();
|
||||
return;
|
||||
}
|
||||
const index = this._memory.getValue().select(this._editor.getModel(), this._editor.getPosition(), completionModel.items);
|
||||
const index = this._memoryService.select(this._editor.getModel()!, this._editor.getPosition()!, completionModel.items);
|
||||
const item = completionModel.items[index];
|
||||
if (!makesTextEdit(item)) {
|
||||
fallback();
|
||||
@@ -368,8 +374,8 @@ export class SuggestController implements IEditorContribution {
|
||||
|
||||
acceptSelectedSuggestion(keepAlternativeSuggestions?: boolean): void {
|
||||
if (this._widget) {
|
||||
const item = this._widget.getFocusedItem();
|
||||
this._onDidSelectItem(item, keepAlternativeSuggestions, true);
|
||||
const item = this._widget.getValue().getFocusedItem();
|
||||
this._onDidSelectItem(item, !!keepAlternativeSuggestions, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,55 +390,55 @@ export class SuggestController implements IEditorContribution {
|
||||
cancelSuggestWidget(): void {
|
||||
if (this._widget) {
|
||||
this._model.cancel();
|
||||
this._widget.hideWidget();
|
||||
this._widget.getValue().hideWidget();
|
||||
}
|
||||
}
|
||||
|
||||
selectNextSuggestion(): void {
|
||||
if (this._widget) {
|
||||
this._widget.selectNext();
|
||||
this._widget.getValue().selectNext();
|
||||
}
|
||||
}
|
||||
|
||||
selectNextPageSuggestion(): void {
|
||||
if (this._widget) {
|
||||
this._widget.selectNextPage();
|
||||
this._widget.getValue().selectNextPage();
|
||||
}
|
||||
}
|
||||
|
||||
selectLastSuggestion(): void {
|
||||
if (this._widget) {
|
||||
this._widget.selectLast();
|
||||
this._widget.getValue().selectLast();
|
||||
}
|
||||
}
|
||||
|
||||
selectPrevSuggestion(): void {
|
||||
if (this._widget) {
|
||||
this._widget.selectPrevious();
|
||||
this._widget.getValue().selectPrevious();
|
||||
}
|
||||
}
|
||||
|
||||
selectPrevPageSuggestion(): void {
|
||||
if (this._widget) {
|
||||
this._widget.selectPreviousPage();
|
||||
this._widget.getValue().selectPreviousPage();
|
||||
}
|
||||
}
|
||||
|
||||
selectFirstSuggestion(): void {
|
||||
if (this._widget) {
|
||||
this._widget.selectFirst();
|
||||
this._widget.getValue().selectFirst();
|
||||
}
|
||||
}
|
||||
|
||||
toggleSuggestionDetails(): void {
|
||||
if (this._widget) {
|
||||
this._widget.toggleDetails();
|
||||
this._widget.getValue().toggleDetails();
|
||||
}
|
||||
}
|
||||
|
||||
toggleSuggestionFocus(): void {
|
||||
if (this._widget) {
|
||||
this._widget.toggleDetailsFocus();
|
||||
this._widget.getValue().toggleDetailsFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -606,7 +612,10 @@ registerEditorCommand(new SuggestCommand({
|
||||
SuggestAlternatives.OtherSuggestions.toNegated(),
|
||||
SnippetController2.InSnippetMode.toNegated()
|
||||
),
|
||||
handler: x => x.triggerSuggestAndAcceptBest('\t'),//todo@joh fallback/default configurable?
|
||||
handler: (x, arg) => {
|
||||
|
||||
x.triggerSuggestAndAcceptBest(isObject(arg) ? { fallback: 'tab', ...arg } : { fallback: 'tab' });
|
||||
},
|
||||
kbOpts: {
|
||||
weight,
|
||||
primary: KeyCode.Tab
|
||||
|
||||
@@ -3,25 +3,28 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ICompletionItem } from 'vs/editor/contrib/suggest/completionModel';
|
||||
|
||||
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 { CompletionItemKind, completionKindFromLegacyString } from 'vs/editor/common/modes';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { CompletionItem } from 'vs/editor/contrib/suggest/suggest';
|
||||
|
||||
export abstract class Memory {
|
||||
|
||||
select(model: ITextModel, pos: IPosition, items: ICompletionItem[]): number {
|
||||
select(model: ITextModel, pos: IPosition, items: CompletionItem[]): number {
|
||||
if (items.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
let topScore = items[0].score;
|
||||
for (let i = 1; i < items.length; i++) {
|
||||
const { score, suggestion } = items[i];
|
||||
const { score, completion: suggestion } = items[i];
|
||||
if (score !== topScore) {
|
||||
// stop when leaving the group of top matches
|
||||
break;
|
||||
@@ -34,16 +37,16 @@ export abstract class Memory {
|
||||
return 0;
|
||||
}
|
||||
|
||||
abstract memorize(model: ITextModel, pos: IPosition, item: ICompletionItem): void;
|
||||
abstract memorize(model: ITextModel, pos: IPosition, item: CompletionItem): void;
|
||||
|
||||
abstract toJSON(): object;
|
||||
abstract toJSON(): object | undefined;
|
||||
|
||||
abstract fromJSON(data: object): void;
|
||||
}
|
||||
|
||||
export class NoMemory extends Memory {
|
||||
|
||||
memorize(model: ITextModel, pos: IPosition, item: ICompletionItem): void {
|
||||
memorize(model: ITextModel, pos: IPosition, item: CompletionItem): void {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@@ -64,20 +67,20 @@ export interface MemItem {
|
||||
|
||||
export class LRUMemory extends Memory {
|
||||
|
||||
private _cache = new LRUCache<string, MemItem>(300, .66);
|
||||
private _cache = new LRUCache<string, MemItem>(300, 0.66);
|
||||
private _seq = 0;
|
||||
|
||||
memorize(model: ITextModel, pos: IPosition, item: ICompletionItem): void {
|
||||
const { label } = item.suggestion;
|
||||
memorize(model: ITextModel, pos: IPosition, item: CompletionItem): void {
|
||||
const { label } = item.completion;
|
||||
const key = `${model.getLanguageIdentifier().language}/${label}`;
|
||||
this._cache.set(key, {
|
||||
touch: this._seq++,
|
||||
type: item.suggestion.kind,
|
||||
insertText: item.suggestion.insertText
|
||||
type: item.completion.kind,
|
||||
insertText: item.completion.insertText
|
||||
});
|
||||
}
|
||||
|
||||
select(model: ITextModel, pos: IPosition, items: ICompletionItem[]): number {
|
||||
select(model: ITextModel, pos: IPosition, items: CompletionItem[]): number {
|
||||
// in order of completions, select the first
|
||||
// that has been used in the past
|
||||
let { word } = model.getWordUntilPosition(pos);
|
||||
@@ -93,7 +96,7 @@ export class LRUMemory extends Memory {
|
||||
let res = -1;
|
||||
let seq = -1;
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const { suggestion } = items[i];
|
||||
const { completion: suggestion } = items[i];
|
||||
const key = `${model.getLanguageIdentifier().language}/${suggestion.label}`;
|
||||
const item = this._cache.get(key);
|
||||
if (item && item.touch > seq && item.type === suggestion.kind && item.insertText === suggestion.insertText) {
|
||||
@@ -134,17 +137,17 @@ export class PrefixMemory extends Memory {
|
||||
private _trie = TernarySearchTree.forStrings<MemItem>();
|
||||
private _seq = 0;
|
||||
|
||||
memorize(model: ITextModel, pos: IPosition, item: ICompletionItem): void {
|
||||
memorize(model: ITextModel, pos: IPosition, item: CompletionItem): void {
|
||||
const { word } = model.getWordUntilPosition(pos);
|
||||
const key = `${model.getLanguageIdentifier().language}/${word}`;
|
||||
this._trie.set(key, {
|
||||
type: item.suggestion.kind,
|
||||
insertText: item.suggestion.insertText,
|
||||
type: item.completion.kind,
|
||||
insertText: item.completion.insertText,
|
||||
touch: this._seq++
|
||||
});
|
||||
}
|
||||
|
||||
select(model: ITextModel, pos: IPosition, items: ICompletionItem[]): number {
|
||||
select(model: ITextModel, pos: IPosition, items: CompletionItem[]): number {
|
||||
let { word } = model.getWordUntilPosition(pos);
|
||||
if (!word) {
|
||||
return super.select(model, pos, items);
|
||||
@@ -156,7 +159,7 @@ export class PrefixMemory extends Memory {
|
||||
}
|
||||
if (item) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let { kind, insertText } = items[i].suggestion;
|
||||
let { kind, insertText } = items[i].completion;
|
||||
if (kind === item.type && insertText === item.insertText) {
|
||||
return i;
|
||||
}
|
||||
@@ -194,35 +197,60 @@ export class PrefixMemory extends Memory {
|
||||
|
||||
export type MemMode = 'first' | 'recentlyUsed' | 'recentlyUsedByPrefix';
|
||||
|
||||
export class SuggestMemories extends Disposable {
|
||||
export class SuggestMemoryService extends Disposable implements ISuggestMemoryService {
|
||||
|
||||
readonly _serviceBrand: any;
|
||||
|
||||
private readonly _storagePrefix = 'suggest/memories';
|
||||
|
||||
private _mode: MemMode;
|
||||
private _strategy: Memory;
|
||||
private readonly _persistSoon: RunOnceScheduler;
|
||||
private _mode: MemMode;
|
||||
private _shareMem: boolean;
|
||||
private _strategy: Memory;
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
@IStorageService private readonly _storageService: IStorageService,
|
||||
@IConfigurationService private readonly _configService: IConfigurationService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._persistSoon = this._register(new RunOnceScheduler(() => this._saveState(editor.getConfiguration().contribInfo.suggest.shareSuggestSelections), 3000));
|
||||
this._setMode(editor.getConfiguration().contribInfo.suggestSelection, editor.getConfiguration().contribInfo.suggest.shareSuggestSelections);
|
||||
this._register(editor.onDidChangeConfiguration(e => e.contribInfo && this._setMode(editor.getConfiguration().contribInfo.suggestSelection, editor.getConfiguration().contribInfo.suggest.shareSuggestSelections)));
|
||||
this._register(_storageService.onWillSaveState(() => this._saveState(editor.getConfiguration().contribInfo.suggest.shareSuggestSelections)));
|
||||
const update = () => {
|
||||
const mode = this._configService.getValue<MemMode>('editor.suggestSelection');
|
||||
const share = this._configService.getValue<boolean>('editor.suggest.shareSuggestSelections');
|
||||
this._update(mode, share, false);
|
||||
};
|
||||
|
||||
this._persistSoon = this._register(new RunOnceScheduler(() => this._saveState(), 500));
|
||||
this._register(_storageService.onWillSaveState(() => this._saveState()));
|
||||
|
||||
this._register(this._configService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('editor.suggestSelection') || e.affectsConfiguration('editor.suggest.shareSuggestSelections')) {
|
||||
update();
|
||||
}
|
||||
}));
|
||||
this._register(this._storageService.onDidChangeStorage(e => {
|
||||
if (e.scope === StorageScope.GLOBAL && e.key.indexOf(this._storagePrefix) === 0) {
|
||||
if (!document.hasFocus()) {
|
||||
// windows that aren't focused have to drop their current
|
||||
// storage value and accept what's stored now
|
||||
this._update(this._mode, this._shareMem, true);
|
||||
}
|
||||
}
|
||||
}));
|
||||
update();
|
||||
}
|
||||
|
||||
private _setMode(mode: MemMode, useGlobalStorageForSuggestions: boolean): void {
|
||||
if (this._mode === mode) {
|
||||
private _update(mode: MemMode, shareMem: boolean, force: boolean): void {
|
||||
if (!force && this._mode === mode && this._shareMem === shareMem) {
|
||||
return;
|
||||
}
|
||||
this._shareMem = shareMem;
|
||||
this._mode = mode;
|
||||
this._strategy = mode === 'recentlyUsedByPrefix' ? new PrefixMemory() : mode === 'recentlyUsed' ? new LRUMemory() : new NoMemory();
|
||||
|
||||
try {
|
||||
const raw = useGlobalStorageForSuggestions ? this._storageService.get(`${this._storagePrefix}/${this._mode}`, StorageScope.GLOBAL) : this._storageService.get(`${this._storagePrefix}/${this._mode}`, StorageScope.WORKSPACE);
|
||||
const scope = shareMem ? StorageScope.GLOBAL : StorageScope.WORKSPACE;
|
||||
const raw = this._storageService.get(`${this._storagePrefix}/${this._mode}`, scope);
|
||||
if (raw) {
|
||||
this._strategy.fromJSON(JSON.parse(raw));
|
||||
}
|
||||
@@ -231,17 +259,29 @@ export class SuggestMemories extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
memorize(model: ITextModel, pos: IPosition, item: ICompletionItem): void {
|
||||
memorize(model: ITextModel, pos: IPosition, item: CompletionItem): void {
|
||||
this._strategy.memorize(model, pos, item);
|
||||
this._persistSoon.schedule();
|
||||
}
|
||||
|
||||
select(model: ITextModel, pos: IPosition, items: ICompletionItem[]): number {
|
||||
select(model: ITextModel, pos: IPosition, items: CompletionItem[]): number {
|
||||
return this._strategy.select(model, pos, items);
|
||||
}
|
||||
|
||||
private _saveState(useGlobalStorageForSuggestions: boolean) {
|
||||
private _saveState() {
|
||||
const raw = JSON.stringify(this._strategy);
|
||||
this._storageService.store(`${this._storagePrefix}/${this._mode}`, raw, useGlobalStorageForSuggestions ? StorageScope.GLOBAL : StorageScope.WORKSPACE);
|
||||
const scope = this._shareMem ? StorageScope.GLOBAL : StorageScope.WORKSPACE;
|
||||
this._storageService.store(`${this._storagePrefix}/${this._mode}`, raw, scope);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const ISuggestMemoryService = createDecorator<ISuggestMemoryService>('ISuggestMemories');
|
||||
|
||||
export interface ISuggestMemoryService {
|
||||
_serviceBrand: any;
|
||||
memorize(model: ITextModel, pos: IPosition, item: CompletionItem): void;
|
||||
select(model: ITextModel, pos: IPosition, items: CompletionItem[]): number;
|
||||
}
|
||||
|
||||
registerSingleton(ISuggestMemoryService, SuggestMemoryService, true);
|
||||
|
||||
@@ -16,7 +16,7 @@ import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { ITextModel, IWordAtPosition } from 'vs/editor/common/model';
|
||||
import { CompletionItemProvider, StandardTokenType, CompletionContext, CompletionProviderRegistry, CompletionTriggerKind } from 'vs/editor/common/modes';
|
||||
import { CompletionModel } from './completionModel';
|
||||
import { ISuggestionItem, getSuggestionComparator, provideSuggestionItems, getSnippetSuggestSupport } from './suggest';
|
||||
import { CompletionItem, getSuggestionComparator, provideSuggestionItems, getSnippetSuggestSupport } from './suggest';
|
||||
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
|
||||
@@ -40,17 +40,17 @@ export interface ISuggestEvent {
|
||||
|
||||
export interface SuggestTriggerContext {
|
||||
readonly auto: boolean;
|
||||
readonly shy?: boolean;
|
||||
readonly shy: boolean;
|
||||
readonly triggerCharacter?: string;
|
||||
}
|
||||
|
||||
export class LineContext {
|
||||
|
||||
static shouldAutoTrigger(editor: ICodeEditor): boolean {
|
||||
const model = editor.getModel();
|
||||
if (!model) {
|
||||
if (!editor.hasModel()) {
|
||||
return false;
|
||||
}
|
||||
const model = editor.getModel();
|
||||
const pos = editor.getPosition();
|
||||
model.tokenizeIfCheap(pos.lineNumber);
|
||||
|
||||
@@ -99,14 +99,14 @@ export class SuggestModel implements IDisposable {
|
||||
private readonly _triggerRefilter = new TimeoutTimer();
|
||||
private _state: State = State.Idle;
|
||||
|
||||
private _requestToken: CancellationTokenSource;
|
||||
private _context: LineContext;
|
||||
private _requestToken?: CancellationTokenSource;
|
||||
private _context?: LineContext;
|
||||
private _currentSelection: Selection;
|
||||
|
||||
private _completionModel: CompletionModel;
|
||||
private readonly _onDidCancel: Emitter<ICancelEvent> = new Emitter<ICancelEvent>();
|
||||
private readonly _onDidTrigger: Emitter<ITriggerEvent> = new Emitter<ITriggerEvent>();
|
||||
private readonly _onDidSuggest: Emitter<ISuggestEvent> = new Emitter<ISuggestEvent>();
|
||||
private _completionModel?: CompletionModel;
|
||||
private readonly _onDidCancel = new Emitter<ICancelEvent>();
|
||||
private readonly _onDidTrigger = new Emitter<ITriggerEvent>();
|
||||
private readonly _onDidSuggest = new Emitter<ISuggestEvent>();
|
||||
|
||||
readonly onDidCancel: Event<ICancelEvent> = this._onDidCancel.event;
|
||||
readonly onDidTrigger: Event<ITriggerEvent> = this._onDidTrigger.event;
|
||||
@@ -116,8 +116,6 @@ export class SuggestModel implements IDisposable {
|
||||
private readonly _editor: ICodeEditor,
|
||||
private readonly _editorWorker: IEditorWorkerService
|
||||
) {
|
||||
this._completionModel = null;
|
||||
this._context = null;
|
||||
this._currentSelection = this._editor.getSelection() || new Selection(1, 1, 1, 1);
|
||||
|
||||
// wire up various listeners
|
||||
@@ -185,7 +183,7 @@ export class SuggestModel implements IDisposable {
|
||||
dispose(this._triggerCharacterListener);
|
||||
|
||||
if (this._editor.getConfiguration().readOnly
|
||||
|| !this._editor.getModel()
|
||||
|| !this._editor.hasModel()
|
||||
|| !this._editor.getConfiguration().contribInfo.suggestOnTriggerCharacters) {
|
||||
|
||||
return;
|
||||
@@ -210,8 +208,8 @@ export class SuggestModel implements IDisposable {
|
||||
if (supports) {
|
||||
// keep existing items that where not computed by the
|
||||
// supports/providers that want to trigger now
|
||||
const items: ISuggestionItem[] = this._completionModel ? this._completionModel.adopt(supports) : undefined;
|
||||
this.trigger({ auto: true, triggerCharacter: lastChar }, Boolean(this._completionModel), values(supports), items);
|
||||
const items: CompletionItem[] | undefined = this._completionModel ? this._completionModel.adopt(supports) : undefined;
|
||||
this.trigger({ auto: true, shy: false, triggerCharacter: lastChar }, Boolean(this._completionModel), values(supports), items);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -223,37 +221,38 @@ export class SuggestModel implements IDisposable {
|
||||
}
|
||||
|
||||
cancel(retrigger: boolean = false): void {
|
||||
|
||||
this._triggerRefilter.cancel();
|
||||
|
||||
if (this._triggerQuickSuggest) {
|
||||
if (this._state !== State.Idle) {
|
||||
this._triggerRefilter.cancel();
|
||||
this._triggerQuickSuggest.cancel();
|
||||
if (this._requestToken) {
|
||||
this._requestToken.cancel();
|
||||
this._requestToken = undefined;
|
||||
}
|
||||
this._state = State.Idle;
|
||||
dispose(this._completionModel);
|
||||
this._completionModel = undefined;
|
||||
this._context = undefined;
|
||||
this._onDidCancel.fire({ retrigger });
|
||||
}
|
||||
|
||||
if (this._requestToken) {
|
||||
this._requestToken.cancel();
|
||||
}
|
||||
|
||||
this._state = State.Idle;
|
||||
dispose(this._completionModel);
|
||||
this._completionModel = null;
|
||||
this._context = null;
|
||||
|
||||
this._onDidCancel.fire({ retrigger });
|
||||
}
|
||||
|
||||
private _updateActiveSuggestSession(): void {
|
||||
if (this._state !== State.Idle) {
|
||||
if (!CompletionProviderRegistry.has(this._editor.getModel())) {
|
||||
if (!this._editor.hasModel() || !CompletionProviderRegistry.has(this._editor.getModel())) {
|
||||
this.cancel();
|
||||
} else {
|
||||
this.trigger({ auto: this._state === State.Auto }, true);
|
||||
this.trigger({ auto: this._state === State.Auto, shy: false }, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _onCursorChange(e: ICursorSelectionChangedEvent): void {
|
||||
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = this._editor.getModel();
|
||||
const prevSelection = this._currentSelection;
|
||||
this._currentSelection = this._editor.getSelection();
|
||||
|
||||
@@ -269,12 +268,7 @@ export class SuggestModel implements IDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CompletionProviderRegistry.has(this._editor.getModel())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = this._editor.getModel();
|
||||
if (!model) {
|
||||
if (!CompletionProviderRegistry.has(model)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -301,12 +295,11 @@ export class SuggestModel implements IDisposable {
|
||||
if (!LineContext.shouldAutoTrigger(this._editor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = this._editor.getModel();
|
||||
const pos = this._editor.getPosition();
|
||||
if (!model) {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
const model = this._editor.getModel();
|
||||
const pos = this._editor.getPosition();
|
||||
// validate enabled now
|
||||
const { quickSuggestions } = this._editor.getConfiguration().contribInfo;
|
||||
if (quickSuggestions === false) {
|
||||
@@ -328,7 +321,7 @@ export class SuggestModel implements IDisposable {
|
||||
}
|
||||
|
||||
// we made it till here -> trigger now
|
||||
this.trigger({ auto: true });
|
||||
this.trigger({ auto: true, shy: false });
|
||||
|
||||
}, this._quickSuggestDelay);
|
||||
|
||||
@@ -339,24 +332,27 @@ export class SuggestModel implements IDisposable {
|
||||
if (this._state === State.Idle) {
|
||||
return;
|
||||
}
|
||||
const model = this._editor.getModel();
|
||||
if (model) {
|
||||
// refine active suggestion
|
||||
this._triggerRefilter.cancelAndSet(() => {
|
||||
const position = this._editor.getPosition();
|
||||
const ctx = new LineContext(model, position, this._state === State.Auto, false);
|
||||
this._onNewContext(ctx);
|
||||
}, 25);
|
||||
}
|
||||
}
|
||||
|
||||
trigger(context: SuggestTriggerContext, retrigger: boolean = false, onlyFrom?: CompletionItemProvider[], existingItems?: ISuggestionItem[]): void {
|
||||
|
||||
const model = this._editor.getModel();
|
||||
|
||||
if (!model) {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
// refine active suggestion
|
||||
this._triggerRefilter.cancelAndSet(() => {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
const model = this._editor.getModel();
|
||||
const position = this._editor.getPosition();
|
||||
const ctx = new LineContext(model, position, this._state === State.Auto, false);
|
||||
this._onNewContext(ctx);
|
||||
}, 25);
|
||||
}
|
||||
|
||||
trigger(context: SuggestTriggerContext, retrigger: boolean = false, onlyFrom?: CompletionItemProvider[], existingItems?: CompletionItem[]): void {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = this._editor.getModel();
|
||||
const auto = context.auto;
|
||||
const ctx = new LineContext(model, this._editor.getPosition(), auto, context.shy);
|
||||
|
||||
@@ -383,8 +379,6 @@ export class SuggestModel implements IDisposable {
|
||||
|
||||
this._requestToken = new CancellationTokenSource();
|
||||
|
||||
// TODO: Remove this workaround - https://github.com/Microsoft/vscode/issues/61917
|
||||
// let wordDistance = Promise.resolve().then(() => WordDistance.create(this._editorWorker, this._editor));
|
||||
let wordDistance = WordDistance.create(this._editorWorker, this._editor);
|
||||
|
||||
let items = provideSuggestionItems(
|
||||
@@ -398,16 +392,18 @@ export class SuggestModel implements IDisposable {
|
||||
|
||||
Promise.all([items, wordDistance]).then(([items, wordDistance]) => {
|
||||
|
||||
this._requestToken.dispose();
|
||||
dispose(this._requestToken);
|
||||
|
||||
if (this._state === State.Idle) {
|
||||
return;
|
||||
}
|
||||
const model = this._editor.getModel();
|
||||
if (!model) {
|
||||
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = this._editor.getModel();
|
||||
|
||||
if (isNonEmptyArray(existingItems)) {
|
||||
const cmpFn = getSuggestionComparator(this._editor.getConfiguration().contribInfo.suggest.snippets);
|
||||
items = items.concat(existingItems).sort(cmpFn);
|
||||
@@ -415,9 +411,9 @@ export class SuggestModel implements IDisposable {
|
||||
|
||||
const ctx = new LineContext(model, this._editor.getPosition(), auto, context.shy);
|
||||
dispose(this._completionModel);
|
||||
this._completionModel = new CompletionModel(items, this._context.column, {
|
||||
this._completionModel = new CompletionModel(items, this._context!.column, {
|
||||
leadingLineContent: ctx.leadingLineContent,
|
||||
characterCountDelta: this._context ? ctx.column - this._context.column : 0
|
||||
characterCountDelta: ctx.column - this._context!.column
|
||||
},
|
||||
wordDistance,
|
||||
this._editor.getConfiguration().contribInfo.suggest
|
||||
@@ -449,7 +445,7 @@ export class SuggestModel implements IDisposable {
|
||||
if (ctx.column < this._context.column) {
|
||||
// typed -> moved cursor LEFT -> retrigger if still on a word
|
||||
if (ctx.leadingWord.word) {
|
||||
this.trigger({ auto: this._context.auto }, true);
|
||||
this.trigger({ auto: this._context.auto, shy: false }, true);
|
||||
} else {
|
||||
this.cancel();
|
||||
}
|
||||
@@ -465,7 +461,7 @@ export class SuggestModel implements IDisposable {
|
||||
// typed -> moved cursor RIGHT & incomple model & still on a word -> retrigger
|
||||
const { incomplete } = this._completionModel;
|
||||
const adopted = this._completionModel.adopt(incomplete);
|
||||
this.trigger({ auto: this._state === State.Auto }, true, values(incomplete), adopted);
|
||||
this.trigger({ auto: this._state === State.Auto, shy: false }, true, values(incomplete), adopted);
|
||||
|
||||
} else {
|
||||
// typed -> moved cursor RIGHT -> update UI
|
||||
@@ -481,7 +477,7 @@ export class SuggestModel implements IDisposable {
|
||||
|
||||
if (LineContext.shouldAutoTrigger(this._editor) && this._context.leadingWord.endColumn < ctx.leadingWord.startColumn) {
|
||||
// retrigger when heading into a new word
|
||||
this.trigger({ auto: this._context.auto }, true);
|
||||
this.trigger({ auto: this._context.auto, shy: false }, true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,19 +7,19 @@ import 'vs/css!./media/suggest';
|
||||
import * as nls from 'vs/nls';
|
||||
import { createMatches } from 'vs/base/common/filters';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { Event, Emitter, chain } from 'vs/base/common/event';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { addClass, append, $, hide, removeClass, show, toggleClass, getDomNodePagePosition, hasClass } from 'vs/base/browser/dom';
|
||||
import { IListVirtualDelegate, IListEvent, IListRenderer } from 'vs/base/browser/ui/list/list';
|
||||
import { IListVirtualDelegate, IListEvent, IListRenderer, IListMouseEvent } from 'vs/base/browser/ui/list/list';
|
||||
import { List } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
|
||||
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
|
||||
import { Context as SuggestContext } from './suggest';
|
||||
import { ICompletionItem, CompletionModel } from './completionModel';
|
||||
import { Context as SuggestContext, CompletionItem } from './suggest';
|
||||
import { CompletionModel } from './completionModel';
|
||||
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||
@@ -63,22 +63,22 @@ export const editorSuggestWidgetHighlightForeground = registerColor('editorSugge
|
||||
|
||||
|
||||
const colorRegExp = /^(#([\da-f]{3}){1,2}|(rgb|hsl)a\(\s*(\d{1,3}%?\s*,\s*){3}(1|0?\.\d+)\)|(rgb|hsl)\(\s*\d{1,3}%?(\s*,\s*\d{1,3}%?){2}\s*\))$/i;
|
||||
function matchesColor(text: string) {
|
||||
function matchesColor(text: string): string | null {
|
||||
return text && text.match(colorRegExp) ? text : null;
|
||||
}
|
||||
|
||||
function canExpandCompletionItem(item: ICompletionItem) {
|
||||
function canExpandCompletionItem(item: CompletionItem | null) {
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
const suggestion = item.suggestion;
|
||||
const suggestion = item.completion;
|
||||
if (suggestion.documentation) {
|
||||
return true;
|
||||
}
|
||||
return (suggestion.detail && suggestion.detail !== suggestion.label);
|
||||
}
|
||||
|
||||
class Renderer implements IListRenderer<ICompletionItem, ISuggestionTemplateData> {
|
||||
class Renderer implements IListRenderer<CompletionItem, ISuggestionTemplateData> {
|
||||
|
||||
constructor(
|
||||
private widget: SuggestWidget,
|
||||
@@ -136,16 +136,16 @@ class Renderer implements IListRenderer<ICompletionItem, ISuggestionTemplateData
|
||||
|
||||
configureFont();
|
||||
|
||||
chain<IConfigurationChangedEvent>(this.editor.onDidChangeConfiguration.bind(this.editor))
|
||||
Event.chain<IConfigurationChangedEvent>(this.editor.onDidChangeConfiguration.bind(this.editor))
|
||||
.filter(e => e.fontInfo || e.contribInfo)
|
||||
.on(configureFont, null, data.disposables);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
renderElement(element: ICompletionItem, _index: number, templateData: ISuggestionTemplateData): void {
|
||||
renderElement(element: CompletionItem, _index: number, templateData: ISuggestionTemplateData): void {
|
||||
const data = <ISuggestionTemplateData>templateData;
|
||||
const suggestion = (<ICompletionItem>element).suggestion;
|
||||
const suggestion = (<CompletionItem>element).completion;
|
||||
|
||||
data.icon.className = 'icon ' + completionKindToCssClass(suggestion.kind);
|
||||
data.colorspan.style.backgroundColor = '';
|
||||
@@ -153,11 +153,11 @@ class Renderer implements IListRenderer<ICompletionItem, ISuggestionTemplateData
|
||||
|
||||
const labelOptions: IIconLabelValueOptions = {
|
||||
labelEscapeNewLines: true,
|
||||
matches: createMatches(element.matches)
|
||||
matches: createMatches(element.score)
|
||||
};
|
||||
|
||||
let color: string;
|
||||
if (suggestion.kind === CompletionItemKind.Color && (color = matchesColor(suggestion.label) || typeof suggestion.documentation === 'string' && matchesColor(suggestion.documentation))) {
|
||||
let color: string | null = null;
|
||||
if (suggestion.kind === CompletionItemKind.Color && ((color = matchesColor(suggestion.label) || typeof suggestion.documentation === 'string' ? matchesColor(suggestion.documentation as any) : null))) {
|
||||
// special logic for 'color' completion items
|
||||
data.icon.className = 'icon customcolor';
|
||||
data.colorspan.style.backgroundColor = color;
|
||||
@@ -165,7 +165,7 @@ class Renderer implements IListRenderer<ICompletionItem, ISuggestionTemplateData
|
||||
} else if (suggestion.kind === CompletionItemKind.File && this._themeService.getIconTheme().hasFileIcons) {
|
||||
// special logic for 'file' completion items
|
||||
data.icon.className = 'icon hide';
|
||||
labelOptions.extraClasses = [].concat(
|
||||
labelOptions.extraClasses = ([] as string[]).concat(
|
||||
getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: suggestion.label }), FileKind.FILE),
|
||||
getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: suggestion.detail }), FileKind.FILE)
|
||||
);
|
||||
@@ -173,7 +173,7 @@ class Renderer implements IListRenderer<ICompletionItem, ISuggestionTemplateData
|
||||
} else if (suggestion.kind === CompletionItemKind.Folder && this._themeService.getIconTheme().hasFolderIcons) {
|
||||
// special logic for 'folder' completion items
|
||||
data.icon.className = 'icon hide';
|
||||
labelOptions.extraClasses = [].concat(
|
||||
labelOptions.extraClasses = ([] as string[]).concat(
|
||||
getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: suggestion.label }), FileKind.FOLDER),
|
||||
getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: suggestion.detail }), FileKind.FOLDER)
|
||||
);
|
||||
@@ -185,7 +185,7 @@ class Renderer implements IListRenderer<ICompletionItem, ISuggestionTemplateData
|
||||
];
|
||||
}
|
||||
|
||||
data.iconLabel.setValue(suggestion.label, undefined, labelOptions);
|
||||
data.iconLabel.setLabel(suggestion.label, undefined, labelOptions);
|
||||
data.typeLabel.textContent = (suggestion.detail || '').replace(/\n.*$/m, '');
|
||||
|
||||
if (canExpandCompletionItem(element)) {
|
||||
@@ -206,10 +206,6 @@ class Renderer implements IListRenderer<ICompletionItem, ISuggestionTemplateData
|
||||
}
|
||||
}
|
||||
|
||||
disposeElement(): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: ISuggestionTemplateData): void {
|
||||
// {{SQL CARBON EDIT}}
|
||||
if (templateData.iconLabel) {
|
||||
@@ -240,7 +236,7 @@ class SuggestionDetails {
|
||||
private header: HTMLElement;
|
||||
private type: HTMLElement;
|
||||
private docs: HTMLElement;
|
||||
private ariaLabel: string;
|
||||
private ariaLabel: string | null;
|
||||
private disposables: IDisposable[];
|
||||
private renderDisposeable: IDisposable;
|
||||
private borderWidth: number = 1;
|
||||
@@ -273,7 +269,7 @@ class SuggestionDetails {
|
||||
|
||||
this.configureFont();
|
||||
|
||||
chain<IConfigurationChangedEvent>(this.editor.onDidChangeConfiguration.bind(this.editor))
|
||||
Event.chain<IConfigurationChangedEvent>(this.editor.onDidChangeConfiguration.bind(this.editor))
|
||||
.filter(e => e.fontInfo)
|
||||
.on(this.configureFont, this, this.disposables);
|
||||
|
||||
@@ -284,7 +280,7 @@ class SuggestionDetails {
|
||||
return this.el;
|
||||
}
|
||||
|
||||
render(item: ICompletionItem): void {
|
||||
render(item: CompletionItem): void {
|
||||
this.renderDisposeable = dispose(this.renderDisposeable);
|
||||
|
||||
if (!item || !canExpandCompletionItem(item)) {
|
||||
@@ -295,19 +291,19 @@ class SuggestionDetails {
|
||||
return;
|
||||
}
|
||||
removeClass(this.el, 'no-docs');
|
||||
if (typeof item.suggestion.documentation === 'string') {
|
||||
if (typeof item.completion.documentation === 'string') {
|
||||
removeClass(this.docs, 'markdown-docs');
|
||||
this.docs.textContent = item.suggestion.documentation;
|
||||
this.docs.textContent = item.completion.documentation;
|
||||
} else {
|
||||
addClass(this.docs, 'markdown-docs');
|
||||
this.docs.innerHTML = '';
|
||||
const renderedContents = this.markdownRenderer.render(item.suggestion.documentation);
|
||||
const renderedContents = this.markdownRenderer.render(item.completion.documentation);
|
||||
this.renderDisposeable = renderedContents;
|
||||
this.docs.appendChild(renderedContents.element);
|
||||
}
|
||||
|
||||
if (item.suggestion.detail) {
|
||||
this.type.innerText = item.suggestion.detail;
|
||||
if (item.completion.detail) {
|
||||
this.type.innerText = item.completion.detail;
|
||||
show(this.type);
|
||||
} else {
|
||||
this.type.innerText = '';
|
||||
@@ -331,11 +327,11 @@ class SuggestionDetails {
|
||||
|
||||
this.ariaLabel = strings.format(
|
||||
'{0}{1}',
|
||||
item.suggestion.detail || '',
|
||||
item.suggestion.documentation ? (typeof item.suggestion.documentation === 'string' ? item.suggestion.documentation : item.suggestion.documentation.value) : '');
|
||||
item.completion.detail || '',
|
||||
item.completion.documentation ? (typeof item.completion.documentation === 'string' ? item.completion.documentation : item.completion.documentation.value) : '');
|
||||
}
|
||||
|
||||
getAriaLabel(): string {
|
||||
getAriaLabel() {
|
||||
return this.ariaLabel;
|
||||
}
|
||||
|
||||
@@ -390,12 +386,12 @@ class SuggestionDetails {
|
||||
}
|
||||
|
||||
export interface ISelectedSuggestion {
|
||||
item: ICompletionItem;
|
||||
item: CompletionItem;
|
||||
index: number;
|
||||
model: CompletionModel;
|
||||
}
|
||||
|
||||
export class SuggestWidget implements IContentWidget, IListVirtualDelegate<ICompletionItem>, IDisposable {
|
||||
export class SuggestWidget implements IContentWidget, IListVirtualDelegate<CompletionItem>, IDisposable {
|
||||
|
||||
private static readonly ID: string = 'editor.widget.suggestWidget';
|
||||
|
||||
@@ -405,19 +401,19 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<IComp
|
||||
// Editor.IContentWidget.allowEditorOverflow
|
||||
readonly allowEditorOverflow = true;
|
||||
|
||||
private state: State;
|
||||
private state: State | null;
|
||||
private isAuto: boolean;
|
||||
private loadingTimeout: any;
|
||||
private currentSuggestionDetails: CancelablePromise<void>;
|
||||
private focusedItem: ICompletionItem;
|
||||
private currentSuggestionDetails: CancelablePromise<void> | null;
|
||||
private focusedItem: CompletionItem | null;
|
||||
private ignoreFocusEvents = false;
|
||||
private completionModel: CompletionModel;
|
||||
private completionModel: CompletionModel | null;
|
||||
|
||||
private element: HTMLElement;
|
||||
private messageElement: HTMLElement;
|
||||
private listElement: HTMLElement;
|
||||
private details: SuggestionDetails;
|
||||
private list: List<ICompletionItem>;
|
||||
private list: List<CompletionItem>;
|
||||
private listHeight: number;
|
||||
|
||||
private suggestWidgetVisible: IContextKey<boolean>;
|
||||
@@ -447,11 +443,11 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<IComp
|
||||
private firstFocusInCurrentList: boolean = false;
|
||||
|
||||
private preferDocPositionTop: boolean = false;
|
||||
private docsPositionPreviousWidgetY: number;
|
||||
private docsPositionPreviousWidgetY: number | null;
|
||||
|
||||
constructor(
|
||||
private editor: ICodeEditor,
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@@ -481,9 +477,8 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<IComp
|
||||
|
||||
this.list = new List(this.listElement, this, [renderer], {
|
||||
useShadows: false,
|
||||
selectOnMouseDown: true,
|
||||
focusOnMouseDown: false,
|
||||
openController: { shouldOpen: () => false }
|
||||
openController: { shouldOpen: () => false },
|
||||
mouseSupport: false
|
||||
});
|
||||
|
||||
this.toDispose = [
|
||||
@@ -493,6 +488,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<IComp
|
||||
}),
|
||||
themeService.onThemeChange(t => this.onThemeChange(t)),
|
||||
editor.onDidLayoutChange(() => this.onEditorLayoutChange()),
|
||||
this.list.onMouseDown(e => this.onListMouseDown(e)),
|
||||
this.list.onSelectionChange(e => this.onListSelection(e)),
|
||||
this.list.onFocusChange(e => this.onListFocus(e)),
|
||||
this.editor.onDidChangeCursorSelection(() => this.onCursorSelectionChanged())
|
||||
@@ -521,37 +517,53 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<IComp
|
||||
}
|
||||
}
|
||||
|
||||
private onListSelection(e: IListEvent<ICompletionItem>): void {
|
||||
private onListMouseDown(e: IListMouseEvent<CompletionItem>): void {
|
||||
if (typeof e.element === 'undefined' || typeof e.index === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.select(e.element, e.index);
|
||||
}
|
||||
|
||||
private onListSelection(e: IListEvent<CompletionItem>): void {
|
||||
if (!e.elements.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const item = e.elements[0];
|
||||
const index = e.indexes[0];
|
||||
this.select(e.elements[0], e.indexes[0]);
|
||||
}
|
||||
|
||||
private select(item: CompletionItem, index: number): void {
|
||||
const completionModel = this.completionModel;
|
||||
|
||||
if (!completionModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
item.resolve(CancellationToken.None).then(() => {
|
||||
this.onDidSelectEmitter.fire({ item, index, model: this.completionModel });
|
||||
alert(nls.localize('suggestionAriaAccepted', "{0}, accepted", item.suggestion.label));
|
||||
this.onDidSelectEmitter.fire({ item, index, model: completionModel });
|
||||
alert(nls.localize('suggestionAriaAccepted', "{0}, accepted", item.completion.label));
|
||||
this.editor.focus();
|
||||
});
|
||||
}
|
||||
|
||||
private _getSuggestionAriaAlertLabel(item: ICompletionItem): string {
|
||||
const isSnippet = item.suggestion.kind === CompletionItemKind.Snippet;
|
||||
private _getSuggestionAriaAlertLabel(item: CompletionItem): string {
|
||||
const isSnippet = item.completion.kind === CompletionItemKind.Snippet;
|
||||
|
||||
if (!canExpandCompletionItem(item)) {
|
||||
return isSnippet ? nls.localize('ariaCurrentSnippetSuggestion', "{0}, snippet suggestion", item.suggestion.label)
|
||||
: nls.localize('ariaCurrentSuggestion', "{0}, suggestion", item.suggestion.label);
|
||||
return isSnippet ? nls.localize('ariaCurrentSnippetSuggestion', "{0}, snippet suggestion", item.completion.label)
|
||||
: nls.localize('ariaCurrentSuggestion', "{0}, suggestion", item.completion.label);
|
||||
} else if (this.expandDocsSettingFromStorage()) {
|
||||
return isSnippet ? nls.localize('ariaCurrentSnippeSuggestionReadDetails', "{0}, snippet suggestion. Reading details. {1}", item.suggestion.label, this.details.getAriaLabel())
|
||||
: nls.localize('ariaCurrenttSuggestionReadDetails', "{0}, suggestion. Reading details. {1}", item.suggestion.label, this.details.getAriaLabel());
|
||||
return isSnippet ? nls.localize('ariaCurrentSnippeSuggestionReadDetails', "{0}, snippet suggestion. Reading details. {1}", item.completion.label, this.details.getAriaLabel())
|
||||
: nls.localize('ariaCurrenttSuggestionReadDetails', "{0}, suggestion. Reading details. {1}", item.completion.label, this.details.getAriaLabel());
|
||||
} else {
|
||||
return isSnippet ? nls.localize('ariaCurrentSnippetSuggestionWithDetails', "{0}, snippet suggestion, has details", item.suggestion.label)
|
||||
: nls.localize('ariaCurrentSuggestionWithDetails', "{0}, suggestion, has details", item.suggestion.label);
|
||||
return isSnippet ? nls.localize('ariaCurrentSnippetSuggestionWithDetails', "{0}, snippet suggestion, has details", item.completion.label)
|
||||
: nls.localize('ariaCurrentSuggestionWithDetails', "{0}, suggestion, has details", item.completion.label);
|
||||
}
|
||||
}
|
||||
|
||||
private _lastAriaAlertLabel: string;
|
||||
private _ariaAlert(newAriaAlertLabel: string): void {
|
||||
private _lastAriaAlertLabel: string | null;
|
||||
private _ariaAlert(newAriaAlertLabel: string | null): void {
|
||||
if (this._lastAriaAlertLabel === newAriaAlertLabel) {
|
||||
return;
|
||||
}
|
||||
@@ -582,7 +594,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<IComp
|
||||
this.details.setBorderWidth(theme.type === 'hc' ? 2 : 1);
|
||||
}
|
||||
|
||||
private onListFocus(e: IListEvent<ICompletionItem>): void {
|
||||
private onListFocus(e: IListEvent<CompletionItem>): void {
|
||||
if (this.ignoreFocusEvents) {
|
||||
return;
|
||||
}
|
||||
@@ -598,6 +610,10 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<IComp
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.completionModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
const item = e.elements[0];
|
||||
const index = e.indexes[0];
|
||||
|
||||
@@ -872,10 +888,12 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<IComp
|
||||
}
|
||||
}
|
||||
|
||||
getFocusedItem(): ISelectedSuggestion {
|
||||
getFocusedItem(): ISelectedSuggestion | undefined {
|
||||
if (this.state !== State.Hidden
|
||||
&& this.state !== State.Empty
|
||||
&& this.state !== State.Loading) {
|
||||
&& this.state !== State.Loading
|
||||
&& this.completionModel
|
||||
) {
|
||||
|
||||
return {
|
||||
item: this.list.getFocusedElements()[0],
|
||||
@@ -944,7 +962,6 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<IComp
|
||||
*/
|
||||
this.telemetryService.publicLog('suggestWidget:expandDetails', this.editor.getTelemetryData());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
showDetails(): void {
|
||||
@@ -992,7 +1009,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<IComp
|
||||
this.onDidHideEmitter.fire(this);
|
||||
}
|
||||
|
||||
getPosition(): IContentWidgetPosition {
|
||||
getPosition(): IContentWidgetPosition | null {
|
||||
if (this.state === State.Hidden) {
|
||||
return null;
|
||||
}
|
||||
@@ -1036,6 +1053,10 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<IComp
|
||||
* Adds the propert classes, margins when positioning the docs to the side
|
||||
*/
|
||||
private adjustDocsPosition() {
|
||||
if (!this.editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lineHeight = this.editor.getConfiguration().fontInfo.lineHeight;
|
||||
const cursorCoords = this.editor.getScrolledVisiblePosition(this.editor.getPosition());
|
||||
const editorCoords = getDomNodePagePosition(this.editor.getDomNode());
|
||||
@@ -1086,7 +1107,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<IComp
|
||||
return;
|
||||
}
|
||||
|
||||
let matches = this.element.style.maxWidth.match(/(\d+)px/);
|
||||
let matches = this.element.style.maxWidth!.match(/(\d+)px/);
|
||||
if (!matches || Number(matches[1]) < this.maxWidgetWidth) {
|
||||
addClass(this.element, 'docs-below');
|
||||
removeClass(this.element, 'docs-side');
|
||||
@@ -1109,11 +1130,11 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<IComp
|
||||
|
||||
// IDelegate
|
||||
|
||||
getHeight(element: ICompletionItem): number {
|
||||
getHeight(element: CompletionItem): number {
|
||||
return this.unfocusedHeight;
|
||||
}
|
||||
|
||||
getTemplateId(element: ICompletionItem): string {
|
||||
getTemplateId(element: CompletionItem): string {
|
||||
return 'suggestion';
|
||||
}
|
||||
|
||||
@@ -1129,13 +1150,13 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<IComp
|
||||
this.state = null;
|
||||
this.currentSuggestionDetails = null;
|
||||
this.focusedItem = null;
|
||||
this.element = null;
|
||||
this.messageElement = null;
|
||||
this.listElement = null;
|
||||
this.element = null!; // StrictNullOverride: nulling out ok in dispose
|
||||
this.messageElement = null!; // StrictNullOverride: nulling out ok in dispose
|
||||
this.listElement = null!; // StrictNullOverride: nulling out ok in dispose
|
||||
this.details.dispose();
|
||||
this.details = null;
|
||||
this.details = null!; // StrictNullOverride: nulling out ok in dispose
|
||||
this.list.dispose();
|
||||
this.list = null;
|
||||
this.list = null!; // StrictNullOverride: nulling out ok in dispose
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
if (this.loadingTimeout) {
|
||||
clearTimeout(this.loadingTimeout);
|
||||
|
||||
@@ -4,39 +4,31 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as assert from 'assert';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { CompletionList, CompletionItemProvider, CompletionItem, CompletionItemKind } from 'vs/editor/common/modes';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { CompletionModel } from 'vs/editor/contrib/suggest/completionModel';
|
||||
import { ISuggestionItem, getSuggestionComparator, ensureLowerCaseVariants } from 'vs/editor/contrib/suggest/suggest';
|
||||
import { CompletionItem, getSuggestionComparator } from 'vs/editor/contrib/suggest/suggest';
|
||||
import { WordDistance } from 'vs/editor/contrib/suggest/wordDistance';
|
||||
|
||||
export function createSuggestItem(label: string, overwriteBefore: number, kind = CompletionItemKind.Property, incomplete: boolean = false, position: IPosition = { lineNumber: 1, column: 1 }): ISuggestionItem {
|
||||
|
||||
return new class implements ISuggestionItem {
|
||||
|
||||
position = position;
|
||||
|
||||
suggestion: CompletionItem = {
|
||||
label,
|
||||
range: { startLineNumber: position.lineNumber, startColumn: position.column - overwriteBefore, endLineNumber: position.lineNumber, endColumn: position.column },
|
||||
insertText: label,
|
||||
kind
|
||||
};
|
||||
|
||||
container: CompletionList = {
|
||||
incomplete,
|
||||
suggestions: [this.suggestion]
|
||||
};
|
||||
|
||||
support: CompletionItemProvider = {
|
||||
provideCompletionItems(): any {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
resolve(): Promise<void> {
|
||||
return null;
|
||||
export function createSuggestItem(label: string, overwriteBefore: number, kind = modes.CompletionItemKind.Property, incomplete: boolean = false, position: IPosition = { lineNumber: 1, column: 1 }, sortText?: string, filterText?: string): CompletionItem {
|
||||
const suggestion: modes.CompletionItem = {
|
||||
label,
|
||||
sortText,
|
||||
filterText,
|
||||
range: { startLineNumber: position.lineNumber, startColumn: position.column - overwriteBefore, endLineNumber: position.lineNumber, endColumn: position.column },
|
||||
insertText: label,
|
||||
kind
|
||||
};
|
||||
const container: modes.CompletionList = {
|
||||
incomplete,
|
||||
suggestions: [suggestion]
|
||||
};
|
||||
const provider: modes.CompletionItemProvider = {
|
||||
provideCompletionItems(): any {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
return new CompletionItem(position, suggestion, container, provider, undefined!);
|
||||
}
|
||||
suite('CompletionModel', function () {
|
||||
|
||||
@@ -100,7 +92,7 @@ suite('CompletionModel', function () {
|
||||
const complete = model.adopt(incomplete);
|
||||
|
||||
assert.equal(incomplete.size, 1);
|
||||
assert.ok(incomplete.has(incompleteItem.support));
|
||||
assert.ok(incomplete.has(incompleteItem.provider));
|
||||
assert.equal(complete.length, 1);
|
||||
assert.ok(complete[0] === completeItem);
|
||||
});
|
||||
@@ -130,7 +122,7 @@ suite('CompletionModel', function () {
|
||||
const complete = model.adopt(incomplete);
|
||||
|
||||
assert.equal(incomplete.size, 1);
|
||||
assert.ok(incomplete.has(incompleteItem1.support));
|
||||
assert.ok(incomplete.has(incompleteItem1.provider));
|
||||
assert.equal(complete.length, 5);
|
||||
});
|
||||
|
||||
@@ -150,18 +142,18 @@ suite('CompletionModel', function () {
|
||||
assert.equal(model.items.length, 4);
|
||||
|
||||
const [a, b, c, d] = model.items;
|
||||
assert.equal(a.suggestion.label, ' </div');
|
||||
assert.equal(b.suggestion.label, ' </tag');
|
||||
assert.equal(c.suggestion.label, 'a');
|
||||
assert.equal(d.suggestion.label, 'p');
|
||||
assert.equal(a.completion.label, ' </div');
|
||||
assert.equal(b.completion.label, ' </tag');
|
||||
assert.equal(c.completion.label, 'a');
|
||||
assert.equal(d.completion.label, 'p');
|
||||
});
|
||||
|
||||
test('keep snippet sorting with prefix: top, #25495', function () {
|
||||
|
||||
model = new CompletionModel([
|
||||
createSuggestItem('Snippet1', 1, CompletionItemKind.Snippet),
|
||||
createSuggestItem('tnippet2', 1, CompletionItemKind.Snippet),
|
||||
createSuggestItem('semver', 1, CompletionItemKind.Property),
|
||||
createSuggestItem('Snippet1', 1, modes.CompletionItemKind.Snippet),
|
||||
createSuggestItem('tnippet2', 1, modes.CompletionItemKind.Snippet),
|
||||
createSuggestItem('semver', 1, modes.CompletionItemKind.Property),
|
||||
], 1, {
|
||||
leadingLineContent: 's',
|
||||
characterCountDelta: 0
|
||||
@@ -169,8 +161,8 @@ suite('CompletionModel', function () {
|
||||
|
||||
assert.equal(model.items.length, 2);
|
||||
const [a, b] = model.items;
|
||||
assert.equal(a.suggestion.label, 'Snippet1');
|
||||
assert.equal(b.suggestion.label, 'semver');
|
||||
assert.equal(a.completion.label, 'Snippet1');
|
||||
assert.equal(b.completion.label, 'semver');
|
||||
assert.ok(a.score < b.score); // snippet really promoted
|
||||
|
||||
});
|
||||
@@ -178,9 +170,9 @@ suite('CompletionModel', function () {
|
||||
test('keep snippet sorting with prefix: bottom, #25495', function () {
|
||||
|
||||
model = new CompletionModel([
|
||||
createSuggestItem('snippet1', 1, CompletionItemKind.Snippet),
|
||||
createSuggestItem('tnippet2', 1, CompletionItemKind.Snippet),
|
||||
createSuggestItem('Semver', 1, CompletionItemKind.Property),
|
||||
createSuggestItem('snippet1', 1, modes.CompletionItemKind.Snippet),
|
||||
createSuggestItem('tnippet2', 1, modes.CompletionItemKind.Snippet),
|
||||
createSuggestItem('Semver', 1, modes.CompletionItemKind.Property),
|
||||
], 1, {
|
||||
leadingLineContent: 's',
|
||||
characterCountDelta: 0
|
||||
@@ -188,16 +180,16 @@ suite('CompletionModel', function () {
|
||||
|
||||
assert.equal(model.items.length, 2);
|
||||
const [a, b] = model.items;
|
||||
assert.equal(a.suggestion.label, 'Semver');
|
||||
assert.equal(b.suggestion.label, 'snippet1');
|
||||
assert.equal(a.completion.label, 'Semver');
|
||||
assert.equal(b.completion.label, 'snippet1');
|
||||
assert.ok(a.score < b.score); // snippet really demoted
|
||||
});
|
||||
|
||||
test('keep snippet sorting with prefix: inline, #25495', function () {
|
||||
|
||||
model = new CompletionModel([
|
||||
createSuggestItem('snippet1', 1, CompletionItemKind.Snippet),
|
||||
createSuggestItem('tnippet2', 1, CompletionItemKind.Snippet),
|
||||
createSuggestItem('snippet1', 1, modes.CompletionItemKind.Snippet),
|
||||
createSuggestItem('tnippet2', 1, modes.CompletionItemKind.Snippet),
|
||||
createSuggestItem('Semver', 1),
|
||||
], 1, {
|
||||
leadingLineContent: 's',
|
||||
@@ -206,15 +198,14 @@ suite('CompletionModel', function () {
|
||||
|
||||
assert.equal(model.items.length, 2);
|
||||
const [a, b] = model.items;
|
||||
assert.equal(a.suggestion.label, 'snippet1');
|
||||
assert.equal(b.suggestion.label, 'Semver');
|
||||
assert.equal(a.completion.label, 'snippet1');
|
||||
assert.equal(b.completion.label, 'Semver');
|
||||
assert.ok(a.score > b.score); // snippet really demoted
|
||||
});
|
||||
|
||||
test('filterText seems ignored in autocompletion, #26874', function () {
|
||||
|
||||
const item1 = createSuggestItem('Map - java.util', 1);
|
||||
item1.suggestion.filterText = 'Map';
|
||||
const item1 = createSuggestItem('Map - java.util', 1, undefined, undefined, undefined, undefined, 'Map');
|
||||
const item2 = createSuggestItem('Map - java.util', 1);
|
||||
|
||||
model = new CompletionModel([item1, item2], 1, {
|
||||
@@ -233,17 +224,8 @@ suite('CompletionModel', function () {
|
||||
|
||||
test('Vscode 1.12 no longer obeys \'sortText\' in completion items (from language server), #26096', function () {
|
||||
|
||||
const item1 = createSuggestItem('<- groups', 2, CompletionItemKind.Property, false, { lineNumber: 1, column: 3 });
|
||||
item1.suggestion.filterText = ' groups';
|
||||
item1.suggestion.sortText = '00002';
|
||||
|
||||
const item2 = createSuggestItem('source', 0, CompletionItemKind.Property, false, { lineNumber: 1, column: 3 });
|
||||
item2.suggestion.filterText = 'source';
|
||||
item2.suggestion.sortText = '00001';
|
||||
|
||||
ensureLowerCaseVariants(item1.suggestion);
|
||||
ensureLowerCaseVariants(item2.suggestion);
|
||||
|
||||
const item1 = createSuggestItem('<- groups', 2, modes.CompletionItemKind.Property, false, { lineNumber: 1, column: 3 }, '00002', ' groups');
|
||||
const item2 = createSuggestItem('source', 0, modes.CompletionItemKind.Property, false, { lineNumber: 1, column: 3 }, '00001', 'source');
|
||||
const items = [item1, item2].sort(getSuggestionComparator('inline'));
|
||||
|
||||
model = new CompletionModel(items, 3, {
|
||||
@@ -254,8 +236,8 @@ suite('CompletionModel', function () {
|
||||
assert.equal(model.items.length, 2);
|
||||
|
||||
const [first, second] = model.items;
|
||||
assert.equal(first.suggestion.label, 'source');
|
||||
assert.equal(second.suggestion.label, '<- groups');
|
||||
assert.equal(first.completion.label, 'source');
|
||||
assert.equal(second.completion.label, '<- groups');
|
||||
});
|
||||
|
||||
test('Score only filtered items when typing more, score all when typing less', function () {
|
||||
@@ -302,9 +284,9 @@ suite('CompletionModel', function () {
|
||||
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`
|
||||
assert.equal(first.completion.label, 'result'); // best with `rult`
|
||||
assert.equal(second.completion.label, 'replyToUser'); // best with `rltu`
|
||||
assert.equal(third.completion.label, 'randomLolut'); // best with `rlut`
|
||||
});
|
||||
|
||||
test('Emmet suggestion not appearing at the top of the list in jsx files, #39518', function () {
|
||||
@@ -322,8 +304,8 @@ suite('CompletionModel', function () {
|
||||
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`
|
||||
assert.equal(first.completion.label, 'form'); // best with `form`
|
||||
assert.equal(second.completion.label, 'form:get'); // best with `form`
|
||||
assert.equal(third.completion.label, 'from'); // best with `from`
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import { CompletionProviderRegistry, CompletionItemKind } from 'vs/editor/common
|
||||
import { provideSuggestionItems } from 'vs/editor/contrib/suggest/suggest';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
|
||||
|
||||
suite('Suggest', function () {
|
||||
@@ -20,21 +21,24 @@ suite('Suggest', function () {
|
||||
|
||||
model = TextModel.createFromString('FOO\nbar\BAR\nfoo', undefined, undefined, URI.parse('foo:bar/path'));
|
||||
registration = CompletionProviderRegistry.register({ pattern: 'bar/path', scheme: 'foo' }, {
|
||||
provideCompletionItems() {
|
||||
provideCompletionItems(_doc, pos) {
|
||||
return {
|
||||
incomplete: false,
|
||||
suggestions: [{
|
||||
label: 'aaa',
|
||||
kind: CompletionItemKind.Snippet,
|
||||
insertText: 'aaa'
|
||||
insertText: 'aaa',
|
||||
range: Range.fromPositions(pos)
|
||||
}, {
|
||||
label: 'zzz',
|
||||
kind: CompletionItemKind.Snippet,
|
||||
insertText: 'zzz'
|
||||
insertText: 'zzz',
|
||||
range: Range.fromPositions(pos)
|
||||
}, {
|
||||
label: 'fff',
|
||||
kind: CompletionItemKind.Property,
|
||||
insertText: 'fff'
|
||||
insertText: 'fff',
|
||||
range: Range.fromPositions(pos)
|
||||
}]
|
||||
};
|
||||
}
|
||||
@@ -49,31 +53,31 @@ suite('Suggest', function () {
|
||||
test('sort - snippet inline', async function () {
|
||||
const items = await provideSuggestionItems(model, new Position(1, 1), 'inline');
|
||||
assert.equal(items.length, 3);
|
||||
assert.equal(items[0].suggestion.label, 'aaa');
|
||||
assert.equal(items[1].suggestion.label, 'fff');
|
||||
assert.equal(items[2].suggestion.label, 'zzz');
|
||||
assert.equal(items[0].completion.label, 'aaa');
|
||||
assert.equal(items[1].completion.label, 'fff');
|
||||
assert.equal(items[2].completion.label, 'zzz');
|
||||
});
|
||||
|
||||
test('sort - snippet top', async function () {
|
||||
const items = await provideSuggestionItems(model, new Position(1, 1), 'top');
|
||||
assert.equal(items.length, 3);
|
||||
assert.equal(items[0].suggestion.label, 'aaa');
|
||||
assert.equal(items[1].suggestion.label, 'zzz');
|
||||
assert.equal(items[2].suggestion.label, 'fff');
|
||||
assert.equal(items[0].completion.label, 'aaa');
|
||||
assert.equal(items[1].completion.label, 'zzz');
|
||||
assert.equal(items[2].completion.label, 'fff');
|
||||
});
|
||||
|
||||
test('sort - snippet bottom', async function () {
|
||||
const items = await provideSuggestionItems(model, new Position(1, 1), 'bottom');
|
||||
assert.equal(items.length, 3);
|
||||
assert.equal(items[0].suggestion.label, 'fff');
|
||||
assert.equal(items[1].suggestion.label, 'aaa');
|
||||
assert.equal(items[2].suggestion.label, 'zzz');
|
||||
assert.equal(items[0].completion.label, 'fff');
|
||||
assert.equal(items[1].completion.label, 'aaa');
|
||||
assert.equal(items[2].completion.label, 'zzz');
|
||||
});
|
||||
|
||||
test('sort - snippet none', async function () {
|
||||
const items = await provideSuggestionItems(model, new Position(1, 1), 'none');
|
||||
assert.equal(items.length, 1);
|
||||
assert.equal(items[0].suggestion.label, 'fff');
|
||||
assert.equal(items[0].completion.label, 'fff');
|
||||
});
|
||||
|
||||
test('only from', function () {
|
||||
@@ -98,7 +102,7 @@ suite('Suggest', function () {
|
||||
registration.dispose();
|
||||
|
||||
assert.equal(items.length, 1);
|
||||
assert.ok(items[0].support === foo);
|
||||
assert.ok(items[0].provider === foo);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,15 +7,15 @@ import * as assert from 'assert';
|
||||
import { LRUMemory, NoMemory, PrefixMemory, Memory } 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';
|
||||
import { CompletionItem } from 'vs/editor/contrib/suggest/suggest';
|
||||
|
||||
suite('SuggestMemories', function () {
|
||||
|
||||
let pos: IPosition;
|
||||
let buffer: ITextModel;
|
||||
let items: ICompletionItem[];
|
||||
let items: CompletionItem[];
|
||||
|
||||
setup(function () {
|
||||
pos = { lineNumber: 1, column: 1 };
|
||||
@@ -29,7 +29,7 @@ suite('SuggestMemories', function () {
|
||||
test('AbstractMemory, select', function () {
|
||||
|
||||
const mem = new class extends Memory {
|
||||
memorize(model: ITextModel, pos: IPosition, item: ICompletionItem): void {
|
||||
memorize(model: ITextModel, pos: IPosition, item: CompletionItem): void {
|
||||
throw new Error('Method not implemented.');
|
||||
} toJSON(): object {
|
||||
throw new Error('Method not implemented.');
|
||||
@@ -43,9 +43,9 @@ suite('SuggestMemories', function () {
|
||||
let item2 = createSuggestItem('bazz', 0);
|
||||
let item3 = createSuggestItem('bazz', 0);
|
||||
let item4 = createSuggestItem('bazz', 0);
|
||||
item1.suggestion.preselect = false;
|
||||
item2.suggestion.preselect = true;
|
||||
item3.suggestion.preselect = true;
|
||||
item1.completion.preselect = false;
|
||||
item2.completion.preselect = true;
|
||||
item3.completion.preselect = true;
|
||||
|
||||
assert.equal(mem.select(buffer, pos, [item1, item2, item3, item4]), 1);
|
||||
});
|
||||
@@ -55,9 +55,9 @@ suite('SuggestMemories', function () {
|
||||
let item2 = createSuggestItem('bazz', 0);
|
||||
let item3 = createSuggestItem('bazz', 0);
|
||||
let item4 = createSuggestItem('bazz', 0);
|
||||
item1.suggestion.preselect = false;
|
||||
item2.suggestion.preselect = true;
|
||||
item3.suggestion.preselect = true;
|
||||
item1.completion.preselect = false;
|
||||
item2.completion.preselect = true;
|
||||
item3.completion.preselect = true;
|
||||
let items = [item1, item2, item3, item4];
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ suite('SuggestMemories', function () {
|
||||
assert.equal(mem.select(buffer, pos, []), 0);
|
||||
|
||||
mem.memorize(buffer, pos, items[0]);
|
||||
mem.memorize(buffer, pos, null);
|
||||
mem.memorize(buffer, pos, null!);
|
||||
});
|
||||
|
||||
test('LRUMemory', () => {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands';
|
||||
import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { TokenizationResult2 } from 'vs/editor/common/core/token';
|
||||
import { Handler } from 'vs/editor/common/editorCommon';
|
||||
@@ -27,6 +28,8 @@ import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/com
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
|
||||
import { ISuggestMemoryService } from 'vs/editor/contrib/suggest/suggestMemory';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
|
||||
export interface Ctor<T> {
|
||||
new(): T;
|
||||
@@ -42,7 +45,15 @@ function createMockEditor(model: TextModel): TestCodeEditor {
|
||||
model: model,
|
||||
serviceCollection: new ServiceCollection(
|
||||
[ITelemetryService, NullTelemetryService],
|
||||
[IStorageService, new InMemoryStorageService()]
|
||||
[IStorageService, new InMemoryStorageService()],
|
||||
[ISuggestMemoryService, new class implements ISuggestMemoryService {
|
||||
_serviceBrand: any;
|
||||
memorize(): void {
|
||||
}
|
||||
select(): number {
|
||||
return -1;
|
||||
}
|
||||
}],
|
||||
),
|
||||
});
|
||||
editor.registerAndInstantiateContribution(SnippetController2);
|
||||
@@ -60,7 +71,7 @@ suite('SuggestModel - Context', function () {
|
||||
|
||||
this._register(TokenizationRegistry.register(this.getLanguageIdentifier().language, {
|
||||
getInitialState: (): IState => NULL_STATE,
|
||||
tokenize: undefined,
|
||||
tokenize: undefined!,
|
||||
tokenize2: (line: string, state: IState): TokenizationResult2 => {
|
||||
const tokensArr: number[] = [];
|
||||
let prevLanguageId: LanguageIdentifier | undefined = undefined;
|
||||
@@ -140,6 +151,11 @@ suite('SuggestModel - Context', function () {
|
||||
suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
|
||||
|
||||
function getDefaultSuggestRange(model: ITextModel, position: Position) {
|
||||
const wordUntil = model.getWordUntilPosition(position);
|
||||
return new Range(position.lineNumber, wordUntil.startColumn, position.lineNumber, wordUntil.endColumn);
|
||||
}
|
||||
|
||||
const alwaysEmptySupport: CompletionItemProvider = {
|
||||
provideCompletionItems(doc, pos): CompletionList {
|
||||
return {
|
||||
@@ -156,7 +172,8 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
suggestions: [{
|
||||
label: doc.getWordUntilPosition(pos).word,
|
||||
kind: CompletionItemKind.Property,
|
||||
insertText: 'foofoo'
|
||||
insertText: 'foofoo',
|
||||
range: getDefaultSuggestRange(doc, pos)
|
||||
}]
|
||||
};
|
||||
}
|
||||
@@ -213,39 +230,27 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
return withOracle(model => {
|
||||
|
||||
return Promise.all([
|
||||
assertEvent(model.onDidCancel, function () {
|
||||
model.cancel();
|
||||
}, function (event) {
|
||||
assert.equal(event.retrigger, false);
|
||||
}),
|
||||
|
||||
assertEvent(model.onDidCancel, function () {
|
||||
model.cancel(true);
|
||||
assertEvent(model.onDidTrigger, function () {
|
||||
model.trigger({ auto: true, shy: false });
|
||||
}, function (event) {
|
||||
assert.equal(event.retrigger, true);
|
||||
}),
|
||||
assert.equal(event.auto, true);
|
||||
|
||||
// cancel on trigger
|
||||
assertEvent(model.onDidCancel, function () {
|
||||
model.trigger({ auto: false });
|
||||
}, function (event) {
|
||||
assert.equal(event.retrigger, false);
|
||||
}),
|
||||
|
||||
assertEvent(model.onDidCancel, function () {
|
||||
model.trigger({ auto: false }, true);
|
||||
}, function (event) {
|
||||
assert.equal(event.retrigger, true);
|
||||
return assertEvent(model.onDidCancel, function () {
|
||||
model.cancel();
|
||||
}, function (event) {
|
||||
assert.equal(event.retrigger, false);
|
||||
});
|
||||
}),
|
||||
|
||||
assertEvent(model.onDidTrigger, function () {
|
||||
model.trigger({ auto: true });
|
||||
model.trigger({ auto: true, shy: false });
|
||||
}, function (event) {
|
||||
assert.equal(event.auto, true);
|
||||
}),
|
||||
|
||||
assertEvent(model.onDidTrigger, function () {
|
||||
model.trigger({ auto: false });
|
||||
model.trigger({ auto: false, shy: false });
|
||||
}, function (event) {
|
||||
assert.equal(event.auto, false);
|
||||
})
|
||||
@@ -261,12 +266,12 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
return withOracle(model => {
|
||||
return Promise.all([
|
||||
assertEvent(model.onDidCancel, function () {
|
||||
model.trigger({ auto: true });
|
||||
model.trigger({ auto: true, shy: false });
|
||||
}, function (event) {
|
||||
assert.equal(event.retrigger, false);
|
||||
}),
|
||||
assertEvent(model.onDidSuggest, function () {
|
||||
model.trigger({ auto: false });
|
||||
model.trigger({ auto: false, shy: false });
|
||||
}, function (event) {
|
||||
assert.equal(event.auto, false);
|
||||
assert.equal(event.isFrozen, false);
|
||||
@@ -290,7 +295,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
assert.equal(event.completionModel.items.length, 1);
|
||||
const [first] = event.completionModel.items;
|
||||
|
||||
assert.equal(first.support, alwaysSomethingSupport);
|
||||
assert.equal(first.provider, alwaysSomethingSupport);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -304,7 +309,8 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
suggestions: [{
|
||||
label: 'My Table',
|
||||
kind: CompletionItemKind.Property,
|
||||
insertText: 'My Table'
|
||||
insertText: 'My Table',
|
||||
range: getDefaultSuggestRange(doc, pos)
|
||||
}]
|
||||
};
|
||||
}
|
||||
@@ -316,7 +322,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
|
||||
return assertEvent(model.onDidSuggest, () => {
|
||||
// make sure completionModel starts here!
|
||||
model.trigger({ auto: true });
|
||||
model.trigger({ auto: true, shy: false });
|
||||
}, event => {
|
||||
|
||||
return assertEvent(model.onDidSuggest, () => {
|
||||
@@ -327,7 +333,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
assert.equal(event.auto, true);
|
||||
assert.equal(event.completionModel.items.length, 1);
|
||||
const [first] = event.completionModel.items;
|
||||
assert.equal(first.suggestion.label, 'My Table');
|
||||
assert.equal(first.completion.label, 'My Table');
|
||||
|
||||
return assertEvent(model.onDidSuggest, () => {
|
||||
editor.setPosition({ lineNumber: 1, column: 3 });
|
||||
@@ -337,7 +343,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
assert.equal(event.auto, true);
|
||||
assert.equal(event.completionModel.items.length, 1);
|
||||
const [first] = event.completionModel.items;
|
||||
assert.equal(first.suggestion.label, 'My Table');
|
||||
assert.equal(first.completion.label, 'My Table');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -390,7 +396,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
assert.equal(event.auto, true);
|
||||
assert.equal(event.completionModel.items.length, 1);
|
||||
const [first] = event.completionModel.items;
|
||||
assert.equal(first.suggestion.label, 'foo.bar');
|
||||
assert.equal(first.completion.label, 'foo.bar');
|
||||
|
||||
return assertEvent(model.onDidSuggest, () => {
|
||||
editor.trigger('keyboard', Handler.Type, { text: '.' });
|
||||
@@ -399,8 +405,8 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
assert.equal(event.auto, true);
|
||||
assert.equal(event.completionModel.items.length, 2);
|
||||
const [first, second] = event.completionModel.items;
|
||||
assert.equal(first.suggestion.label, 'foo.bar');
|
||||
assert.equal(second.suggestion.label, 'boom');
|
||||
assert.equal(first.completion.label, 'foo.bar');
|
||||
assert.equal(second.completion.label, 'boom');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -412,11 +418,11 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
|
||||
return withOracle((model, editor) => {
|
||||
|
||||
editor.getModel().setValue('fo');
|
||||
editor.getModel()!.setValue('fo');
|
||||
editor.setPosition({ lineNumber: 1, column: 3 });
|
||||
|
||||
return assertEvent(model.onDidSuggest, () => {
|
||||
model.trigger({ auto: false });
|
||||
model.trigger({ auto: false, shy: false });
|
||||
}, event => {
|
||||
assert.equal(event.auto, false);
|
||||
assert.equal(event.isFrozen, false);
|
||||
@@ -437,11 +443,11 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
|
||||
return withOracle((model, editor) => {
|
||||
|
||||
editor.getModel().setValue('fo');
|
||||
editor.getModel()!.setValue('fo');
|
||||
editor.setPosition({ lineNumber: 1, column: 3 });
|
||||
|
||||
return assertEvent(model.onDidSuggest, () => {
|
||||
model.trigger({ auto: false });
|
||||
model.trigger({ auto: false, shy: false });
|
||||
}, event => {
|
||||
assert.equal(event.auto, false);
|
||||
assert.equal(event.isFrozen, false);
|
||||
@@ -474,11 +480,11 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
|
||||
return withOracle((model, editor) => {
|
||||
|
||||
editor.getModel().setValue('foo');
|
||||
editor.getModel()!.setValue('foo');
|
||||
editor.setPosition({ lineNumber: 1, column: 4 });
|
||||
|
||||
return assertEvent(model.onDidSuggest, () => {
|
||||
model.trigger({ auto: false });
|
||||
model.trigger({ auto: false, shy: false });
|
||||
}, event => {
|
||||
assert.equal(event.auto, false);
|
||||
assert.equal(event.completionModel.incomplete.size, 1);
|
||||
@@ -511,11 +517,11 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
|
||||
return withOracle((model, editor) => {
|
||||
|
||||
editor.getModel().setValue('foo');
|
||||
editor.getModel()!.setValue('foo');
|
||||
editor.setPosition({ lineNumber: 1, column: 4 });
|
||||
|
||||
return assertEvent(model.onDidSuggest, () => {
|
||||
model.trigger({ auto: false });
|
||||
model.trigger({ auto: false, shy: false });
|
||||
}, event => {
|
||||
assert.equal(event.auto, false);
|
||||
assert.equal(event.completionModel.incomplete.size, 1);
|
||||
@@ -542,7 +548,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
triggerCharacters: ['.'],
|
||||
provideCompletionItems(doc, pos, context): CompletionList {
|
||||
assert.equal(context.triggerKind, CompletionTriggerKind.TriggerCharacter);
|
||||
triggerCharacter = context.triggerCharacter;
|
||||
triggerCharacter = context.triggerCharacter!;
|
||||
return {
|
||||
incomplete: false,
|
||||
suggestions: [
|
||||
@@ -598,7 +604,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'a' });
|
||||
}, event => {
|
||||
assert.equal(event.completionModel.items.length, 1);
|
||||
assert.equal(event.completionModel.items[0].suggestion.label, 'abc');
|
||||
assert.equal(event.completionModel.items[0].completion.label, 'abc');
|
||||
|
||||
return assertEvent(model.onDidSuggest, () => {
|
||||
editor.executeEdits('test', [EditOperation.replace(new Range(1, 1, 1, 2), 'ä')]);
|
||||
@@ -606,7 +612,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
}, event => {
|
||||
// suggest model changed to äbc
|
||||
assert.equal(event.completionModel.items.length, 1);
|
||||
assert.equal(event.completionModel.items[0].suggestion.label, 'äbc');
|
||||
assert.equal(event.completionModel.items[0].completion.label, 'äbc');
|
||||
|
||||
});
|
||||
});
|
||||
@@ -626,7 +632,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
assert.equal(event.completionModel.items.length, 1);
|
||||
const [first] = event.completionModel.items;
|
||||
|
||||
assert.equal(first.support, alwaysSomethingSupport);
|
||||
assert.equal(first.provider, alwaysSomethingSupport);
|
||||
});
|
||||
|
||||
await assertEvent(model.onDidSuggest, () => {
|
||||
@@ -637,7 +643,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
assert.equal(event.completionModel.items.length, 1);
|
||||
const [first] = event.completionModel.items;
|
||||
|
||||
assert.equal(first.support, alwaysSomethingSupport);
|
||||
assert.equal(first.provider, alwaysSomethingSupport);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -674,12 +680,12 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
|
||||
await assertEvent(sugget.onDidSuggest, () => {
|
||||
editor.setPosition({ lineNumber: 1, column: 3 });
|
||||
sugget.trigger({ auto: false });
|
||||
sugget.trigger({ auto: false, shy: false });
|
||||
}, event => {
|
||||
|
||||
assert.equal(event.completionModel.items.length, 1);
|
||||
const [first] = event.completionModel.items;
|
||||
assert.equal(first.suggestion.label, 'bar');
|
||||
assert.equal(first.completion.label, 'bar');
|
||||
|
||||
ctrl._onDidSelectItem({ item: first, index: 0, model: event.completionModel });
|
||||
});
|
||||
@@ -706,7 +712,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
assert.equal(event.completionModel.items.length, 1);
|
||||
const [first] = event.completionModel.items;
|
||||
|
||||
assert.equal(first.support, alwaysSomethingSupport);
|
||||
assert.equal(first.provider, alwaysSomethingSupport);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -721,7 +727,13 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
provideCompletionItems(doc, pos) {
|
||||
return {
|
||||
incomplete: true,
|
||||
suggestions: [{ kind: CompletionItemKind.Folder, label: 'CompleteNot', insertText: 'Incomplete', sortText: 'a', overwriteBefore: pos.column - 1 }],
|
||||
suggestions: [{
|
||||
kind: CompletionItemKind.Folder,
|
||||
label: 'CompleteNot',
|
||||
insertText: 'Incomplete',
|
||||
sortText: 'a',
|
||||
range: getDefaultSuggestRange(doc, pos)
|
||||
}],
|
||||
dispose() { disposeA += 1; }
|
||||
};
|
||||
}
|
||||
@@ -730,7 +742,13 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
provideCompletionItems(doc, pos) {
|
||||
return {
|
||||
incomplete: false,
|
||||
suggestions: [{ kind: CompletionItemKind.Folder, label: 'Complete', insertText: 'Complete', sortText: 'z', overwriteBefore: pos.column - 1 }],
|
||||
suggestions: [{
|
||||
kind: CompletionItemKind.Folder,
|
||||
label: 'Complete',
|
||||
insertText: 'Complete',
|
||||
sortText: 'z',
|
||||
range: getDefaultSuggestRange(doc, pos)
|
||||
}],
|
||||
dispose() { disposeB += 1; }
|
||||
};
|
||||
},
|
||||
|
||||
@@ -8,9 +8,8 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import * as tokenTree from 'vs/editor/contrib/smartSelect/tokenTree';
|
||||
import { CompletionItem, CompletionItemKind } from 'vs/editor/common/modes';
|
||||
|
||||
import { BracketSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/bracketSelections';
|
||||
|
||||
export abstract class WordDistance {
|
||||
|
||||
@@ -18,7 +17,7 @@ export abstract class WordDistance {
|
||||
distance() { return 0; }
|
||||
};
|
||||
|
||||
static create(service: IEditorWorkerService, editor: ICodeEditor): Thenable<WordDistance> {
|
||||
static create(service: IEditorWorkerService, editor: ICodeEditor): Promise<WordDistance> {
|
||||
|
||||
if (!editor.getConfiguration().contribInfo.suggest.localityBonus) {
|
||||
return Promise.resolve(WordDistance.None);
|
||||
@@ -35,51 +34,37 @@ export abstract class WordDistance {
|
||||
return Promise.resolve(WordDistance.None);
|
||||
}
|
||||
|
||||
// use token tree ranges
|
||||
let node = tokenTree.find(tokenTree.build(model), position);
|
||||
let ranges: Range[] = [];
|
||||
while (node) {
|
||||
if (!node.range.isEmpty()) {
|
||||
ranges.push(node.range);
|
||||
return new BracketSelectionRangeProvider().provideSelectionRanges(model, position).then(ranges => {
|
||||
if (!ranges || ranges.length === 0) {
|
||||
return WordDistance.None;
|
||||
}
|
||||
if (node.end.lineNumber - node.start.lineNumber >= 100) {
|
||||
break;
|
||||
}
|
||||
node = node.parent;
|
||||
}
|
||||
ranges.reverse();
|
||||
|
||||
if (ranges.length === 0) {
|
||||
return Promise.resolve(WordDistance.None);
|
||||
}
|
||||
|
||||
return service.computeWordRanges(model.uri, ranges[0]).then(wordRanges => {
|
||||
|
||||
return new class extends WordDistance {
|
||||
distance(anchor: IPosition, suggestion: CompletionItem) {
|
||||
if (!wordRanges || !position.equals(editor.getPosition())) {
|
||||
return 0;
|
||||
}
|
||||
if (suggestion.kind === CompletionItemKind.Keyword) {
|
||||
return 2 << 20;
|
||||
}
|
||||
let word = suggestion.label;
|
||||
let wordLines = wordRanges[word];
|
||||
if (isFalsyOrEmpty(wordLines)) {
|
||||
return 2 << 20;
|
||||
}
|
||||
let idx = binarySearch(wordLines, Range.fromPositions(anchor), Range.compareRangesUsingStarts);
|
||||
let bestWordRange = idx >= 0 ? wordLines[idx] : wordLines[Math.max(0, ~idx - 1)];
|
||||
let blockDistance = ranges.length;
|
||||
for (const range of ranges) {
|
||||
if (!Range.containsRange(range, bestWordRange)) {
|
||||
break;
|
||||
return service.computeWordRanges(model.uri, ranges[0].range).then(wordRanges => {
|
||||
return new class extends WordDistance {
|
||||
distance(anchor: IPosition, suggestion: CompletionItem) {
|
||||
if (!wordRanges || !position.equals(editor.getPosition())) {
|
||||
return 0;
|
||||
}
|
||||
blockDistance -= 1;
|
||||
if (suggestion.kind === CompletionItemKind.Keyword) {
|
||||
return 2 << 20;
|
||||
}
|
||||
let word = suggestion.label;
|
||||
let wordLines = wordRanges[word];
|
||||
if (isFalsyOrEmpty(wordLines)) {
|
||||
return 2 << 20;
|
||||
}
|
||||
let idx = binarySearch(wordLines, Range.fromPositions(anchor), Range.compareRangesUsingStarts);
|
||||
let bestWordRange = idx >= 0 ? wordLines[idx] : wordLines[Math.max(0, ~idx - 1)];
|
||||
let blockDistance = ranges.length;
|
||||
for (const range of ranges) {
|
||||
if (!Range.containsRange(range.range, bestWordRange)) {
|
||||
break;
|
||||
}
|
||||
blockDistance -= 1;
|
||||
}
|
||||
return blockDistance;
|
||||
}
|
||||
return blockDistance;
|
||||
}
|
||||
};
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user