<commit_before>/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/


import * as vscode from 'vscode';
import { expand, createSnippetsRegistry } from '@emmetio/expand-abbreviation';
import { getSyntax, getProfile, extractAbbreviation, isStyleSheet, getNode } from './util';
import parseStylesheet from '@emmetio/css-parser';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
import { DocumentStreamReader } from './bufferStream';

const field = (index, placeholder) => `\${${index}${placeholder ? ':' + placeholder : ''}}`;
const snippetCompletionsCache = new Map<string, vscode.CompletionItem[]>();

export class EmmetCompletionItemProvider implements vscode.CompletionItemProvider {

	public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Thenable<vscode.CompletionList> {

		if (!vscode.workspace.getConfiguration('emmet')['useNewEmmet']) {
			return Promise.resolve(null);
		}

		let completionItems: vscode.CompletionItem[] = [];
		let syntax = getSyntax(document);
		let currentWord = getCurrentWord(document, position);

		let parseContent = isStyleSheet(syntax) ? parseStylesheet : parse;
		let rootNode: Node = parseContent(new DocumentStreamReader(document));
		let currentNode = getNode(rootNode, position);
		let expandedAbbr = this.getExpandedAbbreviation(document, position, syntax, currentNode);

		if (!isStyleSheet(syntax)) {
			if (expandedAbbr) {
				// In non stylesheet like syntax, this extension returns expanded abbr plus posssible abbr completions
				// To differentiate between the 2, the former is given CompletionItemKind.Value so that it gets a different icon
				expandedAbbr.kind = vscode.CompletionItemKind.Value;
			}
			let abbreviationSuggestions = this.getAbbreviationSuggestions(syntax, currentWord, (expandedAbbr && currentWord === expandedAbbr.label));
			completionItems = expandedAbbr ? [expandedAbbr, ...abbreviationSuggestions] : abbreviationSuggestions;
		} else {
			completionItems = expandedAbbr ? [expandedAbbr] : [];
		}

		return Promise.resolve(new vscode.CompletionList(completionItems, true));
	}

	getExpandedAbbreviation(document: vscode.TextDocument, position: vscode.Position, syntax: string, currentNode: Node): vscode.CompletionItem {
		if (!vscode.workspace.getConfiguration('emmet')['showExpandedAbbreviation']) {
			return;
		}
		let [rangeToReplace, wordToExpand] = extractAbbreviation(position);
		if (!rangeToReplace || !wordToExpand) {
			return;
		}
		if (!isValidLocationForEmmetAbbreviation(currentNode, syntax, position)) {
			return;
		}

		let expandedWord = expand(wordToExpand, {
			field: field,
			syntax: syntax,
			profile: getProfile(syntax),
			addons: syntax === 'jsx' ? { 'jsx': true } : null
		});

		let completionitem = new vscode.CompletionItem(wordToExpand);
		completionitem.insertText = new vscode.SnippetString(expandedWord);
		completionitem.documentation = removeTabStops(expandedWord);
		completionitem.range = rangeToReplace;
		completionitem.detail = 'Expand Emmet Abbreviation';


		return completionitem;
	}

	getAbbreviationSuggestions(syntax: string, prefix: string, skipExactMatch: boolean) {
		if (!vscode.workspace.getConfiguration('emmet')['showAbbreviationSuggestions'] || !prefix) {
			return [];
		}

		if (!snippetCompletionsCache.has(syntax)) {
			let registry = createSnippetsRegistry(syntax);
			let completions: vscode.CompletionItem[] = registry.all({ type: 'string' }).map(snippet => {
				let expandedWord = expand(snippet.value, {
					field: field,
					syntax: syntax
				});

				let item = new vscode.CompletionItem(snippet.key);
				item.documentation = removeTabStops(expandedWord);
				item.detail = 'Complete Emmet Abbreviation';
				item.insertText = snippet.key;
				return item;
			});
			snippetCompletionsCache.set(syntax, completions);
		}

		let snippetCompletions = snippetCompletionsCache.get(syntax);

		snippetCompletions = snippetCompletions.filter(x => x.label.startsWith(prefix) && (!skipExactMatch || x.label !== prefix));

		return snippetCompletions;

	}

}



function getCurrentWord(document: vscode.TextDocument, position: vscode.Position): string {
	let wordAtPosition = document.getWordRangeAtPosition(position);
	let currentWord = '';
	if (wordAtPosition && wordAtPosition.start.character < position.character) {
		let word = document.getText(wordAtPosition);
		currentWord = word.substr(0, position.character - wordAtPosition.start.character);
	}

	return currentWord;
}

function removeTabStops(expandedWord: string): string {
	return expandedWord.replace(/\$\{\d+\}/g, '').replace(/\$\{\d+:([^\}]+)\}/g, '$1');
}

function isValidLocationForEmmetAbbreviation(currentNode: Node, syntax: string, position: vscode.Position): boolean {
	if (!currentNode) {
		return true;
	}

	if (isStyleSheet(syntax)) {
		return currentNode.type !== 'rule';
	}

	return position.isAfter(currentNode.open.end);
}



<commit_msg>Use css abbreviations inside html style block. Fixes #28039<commit_after>/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/


import * as vscode from 'vscode';
import { expand, createSnippetsRegistry } from '@emmetio/expand-abbreviation';
import { getSyntax, getProfile, extractAbbreviation, isStyleSheet, getNode } from './util';
import parseStylesheet from '@emmetio/css-parser';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
import { DocumentStreamReader } from './bufferStream';

const field = (index, placeholder) => `\${${index}${placeholder ? ':' + placeholder : ''}}`;
const snippetCompletionsCache = new Map<string, vscode.CompletionItem[]>();

export class EmmetCompletionItemProvider implements vscode.CompletionItemProvider {

	public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Thenable<vscode.CompletionList> {

		if (!vscode.workspace.getConfiguration('emmet')['useNewEmmet']) {
			return Promise.resolve(null);
		}

		let completionItems: vscode.CompletionItem[] = [];
		let syntax = getSyntax(document);
		let currentWord = getCurrentWord(document, position);

		let parseContent = isStyleSheet(syntax) ? parseStylesheet : parse;
		let rootNode: Node = parseContent(new DocumentStreamReader(document));
		let currentNode = getNode(rootNode, position);

		// Inside <style> tag, trigger css abbreviations
		if (!isStyleSheet(syntax) && currentNode && currentNode.name === 'style') {
			syntax = 'css';
		}

		let expandedAbbr = this.getExpandedAbbreviation(document, position, syntax, currentNode);

		if (!isStyleSheet(syntax)) {
			if (expandedAbbr) {
				// In non stylesheet like syntax, this extension returns expanded abbr plus posssible abbr completions
				// To differentiate between the 2, the former is given CompletionItemKind.Value so that it gets a different icon
				expandedAbbr.kind = vscode.CompletionItemKind.Value;
			}
			let abbreviationSuggestions = this.getAbbreviationSuggestions(syntax, currentWord, (expandedAbbr && currentWord === expandedAbbr.label));
			completionItems = expandedAbbr ? [expandedAbbr, ...abbreviationSuggestions] : abbreviationSuggestions;
		} else {
			completionItems = expandedAbbr ? [expandedAbbr] : [];
		}

		return Promise.resolve(new vscode.CompletionList(completionItems, true));
	}

	getExpandedAbbreviation(document: vscode.TextDocument, position: vscode.Position, syntax: string, currentNode: Node): vscode.CompletionItem {
		if (!vscode.workspace.getConfiguration('emmet')['showExpandedAbbreviation']) {
			return;
		}
		let [rangeToReplace, wordToExpand] = extractAbbreviation(position);
		if (!rangeToReplace || !wordToExpand) {
			return;
		}
		if (!isValidLocationForEmmetAbbreviation(currentNode, syntax, position)) {
			return;
		}

		let expandedWord = expand(wordToExpand, {
			field: field,
			syntax: syntax,
			profile: getProfile(syntax),
			addons: syntax === 'jsx' ? { 'jsx': true } : null
		});

		let completionitem = new vscode.CompletionItem(wordToExpand);
		completionitem.insertText = new vscode.SnippetString(expandedWord);
		completionitem.documentation = removeTabStops(expandedWord);
		completionitem.range = rangeToReplace;
		completionitem.detail = 'Expand Emmet Abbreviation';


		return completionitem;
	}

	getAbbreviationSuggestions(syntax: string, prefix: string, skipExactMatch: boolean) {
		if (!vscode.workspace.getConfiguration('emmet')['showAbbreviationSuggestions'] || !prefix) {
			return [];
		}

		if (!snippetCompletionsCache.has(syntax)) {
			let registry = createSnippetsRegistry(syntax);
			let completions: vscode.CompletionItem[] = registry.all({ type: 'string' }).map(snippet => {
				let expandedWord = expand(snippet.value, {
					field: field,
					syntax: syntax
				});

				let item = new vscode.CompletionItem(snippet.key);
				item.documentation = removeTabStops(expandedWord);
				item.detail = 'Complete Emmet Abbreviation';
				item.insertText = snippet.key;
				return item;
			});
			snippetCompletionsCache.set(syntax, completions);
		}

		let snippetCompletions = snippetCompletionsCache.get(syntax);

		snippetCompletions = snippetCompletions.filter(x => x.label.startsWith(prefix) && (!skipExactMatch || x.label !== prefix));

		return snippetCompletions;

	}

}



function getCurrentWord(document: vscode.TextDocument, position: vscode.Position): string {
	let wordAtPosition = document.getWordRangeAtPosition(position);
	let currentWord = '';
	if (wordAtPosition && wordAtPosition.start.character < position.character) {
		let word = document.getText(wordAtPosition);
		currentWord = word.substr(0, position.character - wordAtPosition.start.character);
	}

	return currentWord;
}

function removeTabStops(expandedWord: string): string {
	return expandedWord.replace(/\$\{\d+\}/g, '').replace(/\$\{\d+:([^\}]+)\}/g, '$1');
}

function isValidLocationForEmmetAbbreviation(currentNode: Node, syntax: string, position: vscode.Position): boolean {
	if (!currentNode) {
		return true;
	}

	if (isStyleSheet(syntax)) {
		return currentNode.type !== 'rule';
	}

	return position.isAfter(currentNode.open.end);
}




{'fraction_non_alphanumeric': 0.15243788529796376, 'fraction_numerical': 0.0010274612366897068, 'mean_word_length': 4.30049504950495, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 14, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7550295', 'n_tokens_mistral': 3478, 'n_tokens_neox': 3064, 'n_words': 691}