Skip to content

Commit

Permalink
Emmet: Allow css completions in style tag/attribute, html completions…
Browse files Browse the repository at this point in the history
… in script tags with html type
  • Loading branch information
ramya-rao-a committed Apr 23, 2018
1 parent c314388 commit 79a2512
Show file tree
Hide file tree
Showing 5 changed files with 313 additions and 65 deletions.
34 changes: 28 additions & 6 deletions extensions/emmet/src/abbreviationActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

import * as vscode from 'vscode';
import { Node, HtmlNode, Rule, Property, Stylesheet } from 'EmmetNode';
import { getEmmetHelper, getNode, getInnerRange, getMappingForIncludedLanguages, parseDocument, validate, getEmmetConfiguration, isStyleSheet, getEmmetMode, parsePartialStylesheet } from './util';
import { getEmmetHelper, getNode, getInnerRange, getMappingForIncludedLanguages, parseDocument, validate, getEmmetConfiguration, isStyleSheet, getEmmetMode, parsePartialStylesheet, isStyleAttribute, getEmbeddedCssNodeIfAny } from './util';

const trimRegex = /[\u00a0]*[\d|#|\-|\*|\u2022]+\.?/;
const hexColorRegex = /^#\d+$/;

const allowedMimeTypesInScriptTag = ['text/html', 'text/plain', 'text/x-template', 'text/template'];
const inlineElements = ['a', 'abbr', 'acronym', 'applet', 'b', 'basefont', 'bdo',
'big', 'br', 'button', 'cite', 'code', 'del', 'dfn', 'em', 'font', 'i',
'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'map', 'object', 'q',
Expand Down Expand Up @@ -294,8 +294,24 @@ export function expandEmmetAbbreviation(args: any): Thenable<boolean | undefined
if (!helper.isAbbreviationValid(syntax, abbreviation)) {
return;
}
let currentNode = getNode(rootNode, position, true);
let validateLocation = true;
let syntaxToUse = syntax;

if (editor.document.languageId === 'html') {
if (isStyleAttribute(currentNode, position)) {
syntaxToUse = 'css';
validateLocation = false;
} else {
const embeddedCssNode = getEmbeddedCssNodeIfAny(editor.document, currentNode, position);
if (embeddedCssNode) {
currentNode = getNode(embeddedCssNode, position, true);
syntaxToUse = 'css';
}
}
}

if (!isValidLocationForEmmetAbbreviation(editor.document, rootNode, syntax, position, rangeToReplace)) {
if (validateLocation && !isValidLocationForEmmetAbbreviation(editor.document, rootNode, currentNode, syntaxToUse, position, rangeToReplace)) {
return;
}

Expand All @@ -305,7 +321,7 @@ export function expandEmmetAbbreviation(args: any): Thenable<boolean | undefined
allAbbreviationsSame = false;
}

abbreviationList.push({ syntax, abbreviation, rangeToReplace, filter });
abbreviationList.push({ syntax: syntaxToUse, abbreviation, rangeToReplace, filter });
});

return expandAbbreviationInRange(editor, abbreviationList, allAbbreviationsSame).then(success => {
Expand All @@ -326,12 +342,12 @@ function fallbackTab(): Thenable<boolean | undefined> {
* Works only on html and css/less/scss syntax
* @param document current Text Document
* @param rootNode parsed document
* @param currentNode current node in the parsed document
* @param syntax syntax of the abbreviation
* @param position position to validate
* @param abbreviationRange The range of the abbreviation for which given position is being validated
*/
export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocument, rootNode: Node | undefined, syntax: string, position: vscode.Position, abbreviationRange: vscode.Range): boolean {
const currentNode = rootNode ? getNode(rootNode, position, true) : null;
export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocument, rootNode: Node | undefined, currentNode: Node | null, syntax: string, position: vscode.Position, abbreviationRange: vscode.Range): boolean {
if (isStyleSheet(syntax)) {
const stylesheet = <Stylesheet>rootNode;
if (stylesheet && (stylesheet.comments || []).some(x => position.isAfterOrEqual(x.start) && position.isBeforeOrEqual(x.end))) {
Expand Down Expand Up @@ -407,6 +423,12 @@ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocumen
let start = new vscode.Position(0, 0);

if (currentHtmlNode) {
if (currentHtmlNode.name === 'script') {
return (currentHtmlNode.attributes
&& currentHtmlNode.attributes.some(x => x.name.toString() === 'type'
&& allowedMimeTypesInScriptTag.indexOf(x.value.toString()) > -1));
}

const innerRange = getInnerRange(currentHtmlNode);

// Fix for https://github.com/Microsoft/vscode/issues/28829
Expand Down
91 changes: 75 additions & 16 deletions extensions/emmet/src/defaultCompletionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,39 @@
import * as vscode from 'vscode';
import { Node, Stylesheet } from 'EmmetNode';
import { isValidLocationForEmmetAbbreviation } from './abbreviationActions';
import { getEmmetHelper, getMappingForIncludedLanguages, parsePartialStylesheet, getEmmetConfiguration, getEmmetMode, isStyleSheet, parseDocument, } from './util';
import { getEmmetHelper, getMappingForIncludedLanguages, parsePartialStylesheet, getEmmetConfiguration, getEmmetMode, isStyleSheet, parseDocument, getEmbeddedCssNodeIfAny, isStyleAttribute, getNode } from './util';

export class DefaultCompletionItemProvider implements vscode.CompletionItemProvider {

private lastCompletionType: string | undefined;

public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext): Thenable<vscode.CompletionList | undefined> | undefined {
const completionResult = this.provideCompletionItemsInternal(document, position, token, context);
if (!completionResult) {
this.lastCompletionType = undefined;
return;
}

return completionResult.then(completionList => {
if (!completionList || !completionList.items.length) {
this.lastCompletionType = undefined;
return completionList;
}
const item = completionList.items[0];
const expandedText = item.documentation ? item.documentation.toString() : '';

if (expandedText.startsWith('<')) {
this.lastCompletionType = 'html';
} else if (expandedText.indexOf(':') > 0 && expandedText.endsWith(';')) {
this.lastCompletionType = 'css';
} else {
this.lastCompletionType = undefined;
}
return completionList;
});
}

private provideCompletionItemsInternal(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext): Thenable<vscode.CompletionList | undefined> | undefined {
const emmetConfig = vscode.workspace.getConfiguration('emmet');
const excludedLanguages = emmetConfig['excludeLanguages'] ? emmetConfig['excludeLanguages'] : [];
if (excludedLanguages.indexOf(document.languageId) > -1) {
Expand All @@ -28,29 +56,60 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi
}

const helper = getEmmetHelper();
let validateLocation = syntax === 'html' || syntax === 'jsx' || syntax === 'xml';
let rootNode: Node | undefined = undefined;
let currentNode: Node | null = null;

if (document.languageId === 'html') {
if (context.triggerKind === vscode.CompletionTriggerKind.TriggerForIncompleteCompletions) {
switch (this.lastCompletionType) {
case 'html':
validateLocation = false;
break;
case 'css':
validateLocation = false;
syntax = 'css';
break;
default:
break;
}

}
if (validateLocation) {
rootNode = parseDocument(document, false);
currentNode = getNode(rootNode, position, true);
if (isStyleAttribute(currentNode, position)) {
syntax = 'css';
validateLocation = false;
} else {
const embeddedCssNode = getEmbeddedCssNodeIfAny(document, currentNode, position);
if (embeddedCssNode) {
currentNode = getNode(embeddedCssNode, position, true);
syntax = 'css';
}
}
}

}

const extractAbbreviationResults = helper.extractAbbreviation(document, position, !isStyleSheet(syntax));
if (!extractAbbreviationResults || !helper.isAbbreviationValid(syntax, extractAbbreviationResults.abbreviation)) {
return;
}

let validateLocation = false;
let rootNode: Node | undefined = undefined;

if (context.triggerKind !== vscode.CompletionTriggerKind.TriggerForIncompleteCompletions) {
validateLocation = syntax === 'html' || syntax === 'jsx' || syntax === 'xml' || isStyleSheet(document.languageId);
// If document can be css parsed, get currentNode
if (isStyleSheet(document.languageId)) {
let usePartialParsing = vscode.workspace.getConfiguration('emmet')['optimizeStylesheetParsing'] === true;
rootNode = usePartialParsing && document.lineCount > 1000 ? parsePartialStylesheet(document, position) : <Stylesheet>parseDocument(document, false);
if (!rootNode) {
return;
}
} else if (document.languageId === 'html') {
rootNode = parseDocument(document, false);
if (isStyleSheet(document.languageId) && context.triggerKind !== vscode.CompletionTriggerKind.TriggerForIncompleteCompletions) {
validateLocation = true;
let usePartialParsing = vscode.workspace.getConfiguration('emmet')['optimizeStylesheetParsing'] === true;
rootNode = usePartialParsing && document.lineCount > 1000 ? parsePartialStylesheet(document, position) : <Stylesheet>parseDocument(document, false);
if (!rootNode) {
return;
}
currentNode = getNode(rootNode, position, true);
}

if (validateLocation && !isValidLocationForEmmetAbbreviation(document, rootNode, syntax, position, extractAbbreviationResults.abbreviationRange)) {


if (validateLocation && !isValidLocationForEmmetAbbreviation(document, rootNode, currentNode, syntax, position, extractAbbreviationResults.abbreviationRange)) {
return;
}

Expand Down
Loading

0 comments on commit 79a2512

Please sign in to comment.