123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- import { quickSelect } from '../../../base/common/arrays.js';
- import { anyScore, fuzzyScore, FuzzyScore, fuzzyScoreGracefulAggressive } from '../../../base/common/filters.js';
- import { compareIgnoreCase } from '../../../base/common/strings.js';
- export class LineContext {
- constructor(leadingLineContent, characterCountDelta) {
- this.leadingLineContent = leadingLineContent;
- this.characterCountDelta = characterCountDelta;
- }
- }
- /**
- * Sorted, filtered completion view model
- * */
- export class CompletionModel {
- constructor(items, column, lineContext, wordDistance, options, snippetSuggestions, clipboardText) {
- this.clipboardText = clipboardText;
- this._snippetCompareFn = CompletionModel._compareCompletionItems;
- this._items = items;
- this._column = column;
- this._wordDistance = wordDistance;
- this._options = options;
- this._refilterKind = 1 /* All */;
- this._lineContext = lineContext;
- if (snippetSuggestions === 'top') {
- this._snippetCompareFn = CompletionModel._compareCompletionItemsSnippetsUp;
- }
- else if (snippetSuggestions === 'bottom') {
- this._snippetCompareFn = CompletionModel._compareCompletionItemsSnippetsDown;
- }
- }
- get lineContext() {
- return this._lineContext;
- }
- set lineContext(value) {
- if (this._lineContext.leadingLineContent !== value.leadingLineContent
- || this._lineContext.characterCountDelta !== value.characterCountDelta) {
- this._refilterKind = this._lineContext.characterCountDelta < value.characterCountDelta && this._filteredItems ? 2 /* Incr */ : 1 /* All */;
- this._lineContext = value;
- }
- }
- get items() {
- this._ensureCachedState();
- return this._filteredItems;
- }
- get allProvider() {
- this._ensureCachedState();
- return this._providerInfo.keys();
- }
- get incomplete() {
- this._ensureCachedState();
- const result = new Set();
- for (let [provider, incomplete] of this._providerInfo) {
- if (incomplete) {
- result.add(provider);
- }
- }
- return result;
- }
- adopt(except) {
- let res = [];
- for (let i = 0; i < this._items.length;) {
- if (!except.has(this._items[i].provider)) {
- res.push(this._items[i]);
- // unordered removed
- this._items[i] = this._items[this._items.length - 1];
- this._items.pop();
- }
- else {
- // continue with next item
- i++;
- }
- }
- this._refilterKind = 1 /* All */;
- return res;
- }
- get stats() {
- this._ensureCachedState();
- return this._stats;
- }
- _ensureCachedState() {
- if (this._refilterKind !== 0 /* Nothing */) {
- this._createCachedState();
- }
- }
- _createCachedState() {
- this._providerInfo = new Map();
- const labelLengths = [];
- const { leadingLineContent, characterCountDelta } = this._lineContext;
- let word = '';
- let wordLow = '';
- // incrementally filter less
- const source = this._refilterKind === 1 /* All */ ? this._items : this._filteredItems;
- const target = [];
- // picks a score function based on the number of
- // items that we have to score/filter and based on the
- // user-configuration
- const scoreFn = (!this._options.filterGraceful || source.length > 2000) ? fuzzyScore : fuzzyScoreGracefulAggressive;
- for (let i = 0; i < source.length; i++) {
- const item = source[i];
- if (item.isInvalid) {
- continue; // SKIP invalid items
- }
- // collect all support, know if their result is incomplete
- this._providerInfo.set(item.provider, Boolean(item.container.incomplete));
- // '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 - item.editStart.column;
- const wordLen = overwriteBefore + characterCountDelta - (item.position.column - this._column);
- if (word.length !== wordLen) {
- word = wordLen === 0 ? '' : leadingLineContent.slice(-wordLen);
- wordLow = word.toLowerCase();
- }
- // remember the word against which this item was
- // scored
- item.word = word;
- if (wordLen === 0) {
- // when there is nothing to score against, don't
- // event try to do. Use a const rank and rely on
- // 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 = FuzzyScore.Default;
- }
- else {
- // skip word characters that are whitespace until
- // we have hit the replace range (overwriteBefore)
- let wordPos = 0;
- while (wordPos < overwriteBefore) {
- const ch = word.charCodeAt(wordPos);
- if (ch === 32 /* Space */ || ch === 9 /* Tab */) {
- wordPos += 1;
- }
- else {
- break;
- }
- }
- 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 = FuzzyScore.Default;
- }
- 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, item.completion.filterText, item.filterTextLow, 0, false);
- if (!match) {
- continue; // NO match
- }
- if (compareIgnoreCase(item.completion.filterText, item.textLabel) === 0) {
- // filterText and label are actually the same -> use good highlights
- item.score = match;
- }
- else {
- // re-run the scorer on the label in the hope of a result BUT use the rank
- // of the filterText-match
- item.score = anyScore(word, wordLow, wordPos, item.textLabel, 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, item.textLabel, item.labelLow, 0, false);
- if (!match) {
- continue; // NO match
- }
- item.score = match;
- }
- }
- item.idx = i;
- item.distance = this._wordDistance.distance(item.position, item.completion);
- target.push(item);
- // update stats
- labelLengths.push(item.textLabel.length);
- }
- this._filteredItems = target.sort(this._snippetCompareFn);
- this._refilterKind = 0 /* Nothing */;
- this._stats = {
- pLabelLen: labelLengths.length ?
- quickSelect(labelLengths.length - .85, labelLengths, (a, b) => a - b)
- : 0
- };
- }
- static _compareCompletionItems(a, b) {
- if (a.score[0] > b.score[0]) {
- return -1;
- }
- else if (a.score[0] < b.score[0]) {
- return 1;
- }
- else if (a.distance < b.distance) {
- return -1;
- }
- else if (a.distance > b.distance) {
- return 1;
- }
- else if (a.idx < b.idx) {
- return -1;
- }
- else if (a.idx > b.idx) {
- return 1;
- }
- else {
- return 0;
- }
- }
- static _compareCompletionItemsSnippetsDown(a, b) {
- if (a.completion.kind !== b.completion.kind) {
- if (a.completion.kind === 27 /* Snippet */) {
- return 1;
- }
- else if (b.completion.kind === 27 /* Snippet */) {
- return -1;
- }
- }
- return CompletionModel._compareCompletionItems(a, b);
- }
- static _compareCompletionItemsSnippetsUp(a, b) {
- if (a.completion.kind !== b.completion.kind) {
- if (a.completion.kind === 27 /* Snippet */) {
- return -1;
- }
- else if (b.completion.kind === 27 /* Snippet */) {
- return 1;
- }
- }
- return CompletionModel._compareCompletionItems(a, b);
- }
- }
|