src/vs/editor/contrib/suggest/browser/suggest.ts/onCompletionList
export async function provideSuggestionItems(
	registry: LanguageFeatureRegistry<languages.CompletionItemProvider>,
	model: ITextModel,
	position: Position,
	options: CompletionOptions = CompletionOptions.default,
	context: languages.CompletionContext = { triggerKind: languages.CompletionTriggerKind.Invoke },
	token: CancellationToken = CancellationToken.None
): Promise<CompletionItemModel> {

	const sw = new StopWatch(true);
	position = position.clone();

	const word = model.getWordAtPosition(position);
	const defaultReplaceRange = word ? new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn) : Range.fromPositions(position);
	const defaultRange = { replace: defaultReplaceRange, insert: defaultReplaceRange.setEndPosition(position.lineNumber, position.column) };

	const result: CompletionItem[] = [];
	const disposables = new DisposableStore();
	const durations: CompletionDurationEntry[] = [];
	let needsClipboard = false;

	const onCompletionList = (provider: languages.CompletionItemProvider, container: languages.CompletionList | null | undefined, sw: StopWatch): boolean => {
		let didAddResult = false;
		if (!container) {
			return didAddResult;
		}
		for (let suggestion of container.suggestions) {
			if (!options.kindFilter.has(suggestion.kind)) {
				// skip if not showing deprecated suggestions
				if (!options.showDeprecated && suggestion?.tags?.includes(languages.CompletionItemTag.Deprecated)) {
					continue;
				}
				// fill in default range when missing
				if (!suggestion.range) {
					suggestion.range = defaultRange;
				}
				// fill in default sortText when missing
				if (!suggestion.sortText) {
					suggestion.sortText = typeof suggestion.label === 'string' ? suggestion.label : suggestion.label.label;
				}
				if (!needsClipboard && suggestion.insertTextRules && suggestion.insertTextRules & languages.CompletionItemInsertTextRule.InsertAsSnippet) {
					needsClipboard = SnippetParser.guessNeedsClipboard(suggestion.insertText);
				}
				result.push(new CompletionItem(position, suggestion, container, provider));
				didAddResult = true;
			}
		}
		if (isDisposable(container)) {
			disposables.add(container);
		}
		durations.push({
			providerName: provider._debugDisplayName ?? 'unknown_provider', elapsedProvider: container.duration ?? -1, elapsedOverall: sw.elapsed()
		});
		return didAddResult;
	};

	// ask for snippets in parallel to asking "real" providers. Only do something if configured to
	// do so - no snippet filter, no special-providers-only request
	const snippetCompletions = (async () => {
		if (!_snippetSuggestSupport || options.kindFilter.has(languages.CompletionItemKind.Snippet)) {
			return;
		}
		if (options.providerFilter.size > 0 && !options.providerFilter.has(_snippetSuggestSupport)) {
			return;
		}
		const sw = new StopWatch(true);
		const list = await _snippetSuggestSupport.provideCompletionItems(model, position, context, token);
		onCompletionList(_snippetSuggestSupport, list, sw);
	})();

	// add suggestions from contributed providers - providers are ordered in groups of
	// equal score and once a group produces a result the process stops
	// get provider groups, always add snippet suggestion provider
	for (let providerGroup of registry.orderedGroups(model)) {

		// for each support in the group ask for suggestions
		let didAddResult = false;
		await Promise.all(providerGroup.map(async provider => {
			if (options.providerFilter.size > 0 && !options.providerFilter.has(provider)) {
				return;
			}
			try {
				const sw = new StopWatch(true);
				const list = await provider.provideCompletionItems(model, position, context, token);
				didAddResult = onCompletionList(provider, list, sw) || didAddResult;
			} catch (err) {
				onUnexpectedExternalError(err);
			}
		}));

		if (didAddResult || token.isCancellationRequested) {
			break;
		}
	}

	await snippetCompletions;

	if (token.isCancellationRequested) {
		disposables.dispose();
		return Promise.reject<any>(new CancellationError());
	}

	return new CompletionItemModel(
		result.sort(getSuggestionComparator(options.snippetSortOrder)),
		needsClipboard,
		{ entries: durations, elapsed: sw.elapsed() },
		disposables,
	);
}