monaco-editor
Version:
A browser based code editor
221 lines (220 loc) • 9.55 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { first } from '../../../base/common/async.js';
import { isFalsyOrEmpty } from '../../../base/common/arrays.js';
import { assign } from '../../../base/common/objects.js';
import { onUnexpectedExternalError, canceled } from '../../../base/common/errors.js';
import { registerDefaultLanguageCommand } from '../../browser/editorExtensions.js';
import { CompletionProviderRegistry } from '../../common/modes.js';
import { RawContextKey } from '../../../platform/contextkey/common/contextkey.js';
import { CancellationToken } from '../../../base/common/cancellation.js';
import { Range } from '../../common/core/range.js';
export var Context = {
Visible: new RawContextKey('suggestWidgetVisible', false),
MultipleSuggestions: new RawContextKey('suggestWidgetMultipleSuggestions', false),
MakesTextEdit: new RawContextKey('suggestionMakesTextEdit', true),
AcceptSuggestionsOnEnter: new RawContextKey('acceptSuggestionOnEnter', true)
};
var _snippetSuggestSupport;
export function getSnippetSuggestSupport() {
return _snippetSuggestSupport;
}
export function provideSuggestionItems(model, position, snippetConfig, onlyFrom, context, token) {
if (snippetConfig === void 0) { snippetConfig = 'bottom'; }
if (token === void 0) { token = CancellationToken.None; }
var allSuggestions = [];
var acceptSuggestion = createSuggesionFilter(snippetConfig);
var wordUntil = model.getWordUntilPosition(position);
var defaultRange = new Range(position.lineNumber, wordUntil.startColumn, position.lineNumber, wordUntil.endColumn);
position = position.clone();
// get provider groups, always add snippet suggestion provider
var supports = CompletionProviderRegistry.orderedGroups(model);
// add snippets provider unless turned off
if (snippetConfig !== 'none' && _snippetSuggestSupport) {
supports.unshift([_snippetSuggestSupport]);
}
var suggestConext = context || { triggerKind: 0 /* Invoke */ };
// add suggestions from contributed providers - providers are ordered in groups of
// equal score and once a group produces a result the process stops
var hasResult = false;
var factory = supports.map(function (supports) { return function () {
// for each support in the group ask for suggestions
return Promise.all(supports.map(function (support) {
if (!isFalsyOrEmpty(onlyFrom) && onlyFrom.indexOf(support) < 0) {
return undefined;
}
return Promise.resolve(support.provideCompletionItems(model, position, suggestConext, token)).then(function (container) {
var len = allSuggestions.length;
if (container && !isFalsyOrEmpty(container.suggestions)) {
for (var _i = 0, _a = container.suggestions; _i < _a.length; _i++) {
var suggestion = _a[_i];
if (acceptSuggestion(suggestion)) {
// fill in default range when missing
if (!suggestion.range) {
suggestion.range = defaultRange;
}
// fill in lower-case text
ensureLowerCaseVariants(suggestion);
allSuggestions.push({
position: position,
container: container,
suggestion: suggestion,
support: support,
resolve: createSuggestionResolver(support, suggestion, model, position)
});
}
}
}
if (len !== allSuggestions.length && support !== _snippetSuggestSupport) {
hasResult = true;
}
}, onUnexpectedExternalError);
}));
}; });
var result = first(factory, function () {
// stop on result or cancellation
return hasResult || token.isCancellationRequested;
}).then(function () {
if (token.isCancellationRequested) {
return Promise.reject(canceled());
}
return allSuggestions.sort(getSuggestionComparator(snippetConfig));
});
// result.then(items => {
// console.log(model.getWordUntilPosition(position), items.map(item => `${item.suggestion.label}, type=${item.suggestion.type}, incomplete?${item.container.incomplete}, overwriteBefore=${item.suggestion.overwriteBefore}`));
// return items;
// }, err => {
// console.warn(model.getWordUntilPosition(position), err);
// });
return result;
}
export function ensureLowerCaseVariants(suggestion) {
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, suggestion, model, position) {
var cached;
return function (token) {
if (!cached) {
if (typeof provider.resolveCompletionItem === 'function') {
cached = Promise.resolve(provider.resolveCompletionItem(model, position, suggestion, token)).then(function (value) { assign(suggestion, value); });
}
else {
cached = Promise.resolve(void 0);
}
}
return cached;
};
}
function createSuggesionFilter(snippetConfig) {
if (snippetConfig === 'none') {
return function (suggestion) { return suggestion.kind !== 25 /* Snippet */; };
}
else {
return function () { return true; };
}
}
function defaultComparator(a, b) {
// check with 'sortText'
if (a.suggestion._sortTextLow && b.suggestion._sortTextLow) {
if (a.suggestion._sortTextLow < b.suggestion._sortTextLow) {
return -1;
}
else if (a.suggestion._sortTextLow > b.suggestion._sortTextLow) {
return 1;
}
}
// check with 'label'
if (a.suggestion.label < b.suggestion.label) {
return -1;
}
else if (a.suggestion.label > b.suggestion.label) {
return 1;
}
// check with 'type'
return a.suggestion.kind - b.suggestion.kind;
}
function snippetUpComparator(a, b) {
if (a.suggestion.kind !== b.suggestion.kind) {
if (a.suggestion.kind === 25 /* Snippet */) {
return -1;
}
else if (b.suggestion.kind === 25 /* Snippet */) {
return 1;
}
}
return defaultComparator(a, b);
}
function snippetDownComparator(a, b) {
if (a.suggestion.kind !== b.suggestion.kind) {
if (a.suggestion.kind === 25 /* Snippet */) {
return 1;
}
else if (b.suggestion.kind === 25 /* Snippet */) {
return -1;
}
}
return defaultComparator(a, b);
}
export function getSuggestionComparator(snippetConfig) {
if (snippetConfig === 'top') {
return snippetUpComparator;
}
else if (snippetConfig === 'bottom') {
return snippetDownComparator;
}
else {
return defaultComparator;
}
}
registerDefaultLanguageCommand('_executeCompletionItemProvider', function (model, position, args) {
var result = {
incomplete: false,
suggestions: []
};
var resolving = [];
var maxItemsToResolve = args['maxItemsToResolve'] || 0;
return provideSuggestionItems(model, position).then(function (items) {
for (var _i = 0, items_1 = items; _i < items_1.length; _i++) {
var item = items_1[_i];
if (resolving.length < maxItemsToResolve) {
resolving.push(item.resolve(CancellationToken.None));
}
result.incomplete = result.incomplete || item.container.incomplete;
result.suggestions.push(item.suggestion);
}
}).then(function () {
return Promise.all(resolving);
}).then(function () {
return result;
});
});
var _provider = new /** @class */ (function () {
function class_1() {
this.onlyOnceSuggestions = [];
}
class_1.prototype.provideCompletionItems = function () {
var suggestions = this.onlyOnceSuggestions.slice(0);
var result = { suggestions: suggestions };
this.onlyOnceSuggestions.length = 0;
return result;
};
return class_1;
}());
CompletionProviderRegistry.register('*', _provider);
export function showSimpleSuggestions(editor, suggestions) {
setTimeout(function () {
var _a;
(_a = _provider.onlyOnceSuggestions).push.apply(_a, suggestions);
editor.getContribution('editor.contrib.suggestController').triggerSuggest([_provider]);
}, 0);
}