diff --git a/extensions/emmet/src/abbreviationActions.ts b/extensions/emmet/src/abbreviationActions.ts index 480699eafe1bfcb1232017ca1a759c542b2f14b7..70c87da5bc6ad8d8ce9ad652cfb447a43d9a66f1 100644 --- a/extensions/emmet/src/abbreviationActions.ts +++ b/extensions/emmet/src/abbreviationActions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { Node, HtmlNode, Rule } from 'EmmetNode'; +import { Node, HtmlNode, Rule, Property } from 'EmmetNode'; import { getEmmetHelper, getNode, getInnerRange, getMappingForIncludedLanguages, parseDocument, validate, getEmmetConfiguration, isStyleSheet, getEmmetMode } from './util'; const trimRegex = /[\u00a0]*[\d|#|\-|\*|\u2022]+\.?/; @@ -223,6 +223,23 @@ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocumen return true; } + // Fix for https://github.com/Microsoft/vscode/issues/34162 + // Other than sass, stylus, we can make use of the terminator tokens to validate position + if (syntax !== 'sass' && syntax !== 'stylus' && currentNode.type === 'property') { + const propertyNode = currentNode; + if (propertyNode.terminatorToken + && propertyNode.separator + && position.isAfterOrEqual(propertyNode.separatorToken.end) + && position.isBeforeOrEqual(propertyNode.terminatorToken.start)) { + return false; + } + if (!propertyNode.terminatorToken + && propertyNode.separator + && position.isAfterOrEqual(propertyNode.separatorToken.end)) { + return false; + } + } + // If current node is a rule or at-rule, then perform additional checks to ensure // emmet suggestions are not provided in the rule selector if (currentNode.type !== 'rule' && currentNode.type !== 'at-rule') { @@ -242,7 +259,10 @@ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocumen if (currentCssNode.parent && (currentCssNode.parent.type === 'rule' || currentCssNode.parent.type === 'at-rule') && currentCssNode.selectorToken - && position.line !== currentCssNode.selectorToken.end.line) { + && position.line !== currentCssNode.selectorToken.end.line + && currentCssNode.selectorToken.start.character === abbreviationRange.start.character + && currentCssNode.selectorToken.start.line === abbreviationRange.start.line + ) { return true; } diff --git a/extensions/emmet/src/test/abbreviationAction.test.ts b/extensions/emmet/src/test/abbreviationAction.test.ts index 101da90685c808bca0edc885145ebc24e58f03de..fdfc373e96faa125b8595d7f351f4337cbe513db 100644 --- a/extensions/emmet/src/test/abbreviationAction.test.ts +++ b/extensions/emmet/src/test/abbreviationAction.test.ts @@ -42,6 +42,13 @@ const scssContents = ` p40 } } +.foo { + margin: 10px; + margin: a + .hoo { + color: #000; + } +} `; const htmlContents = ` @@ -373,6 +380,71 @@ suite('Tests for Expand Abbreviations (CSS)', () => { }); }); + test('Skip when typing property values when there is a property in the next line (CSS)', () => { + const testContent = ` +.foo { + margin: a + margin: 10px; +} + `; + + return withRandomFileEditor(testContent, 'css', (editor, doc) => { + editor.selection = new Selection(2, 10, 2, 10); + return expandEmmetAbbreviation(null).then(() => { + assert.equal(editor.document.getText(), testContent); + const cancelSrc = new CancellationTokenSource(); + const completionPromise = completionProvider.provideCompletionItems(editor.document, new Position(2, 10), cancelSrc.token); + if (completionPromise) { + assert.equal(1, 2, `Invalid completion at property value`); + } + return Promise.resolve(); + }); + }); + }); + + test('Skip when typing property values when there is a property in the previous line (CSS)', () => { + const testContent = ` +.foo { + margin: 10px; + margin: a +} + `; + + return withRandomFileEditor(testContent, 'css', (editor, doc) => { + editor.selection = new Selection(3, 10, 3, 10); + return expandEmmetAbbreviation(null).then(() => { + assert.equal(editor.document.getText(), testContent); + const cancelSrc = new CancellationTokenSource(); + const completionPromise = completionProvider.provideCompletionItems(editor.document, new Position(3, 10), cancelSrc.token); + if (completionPromise) { + assert.equal(1, 2, `Invalid completion at property value`); + } + return Promise.resolve(); + }); + }); + }); + + test('Skip when typing property values when it is the only property in the rule (CSS)', () => { + const testContent = ` +.foo { + margin: a +} + `; + + return withRandomFileEditor(testContent, 'css', (editor, doc) => { + editor.selection = new Selection(2, 10, 2, 10); + return expandEmmetAbbreviation(null).then(() => { + assert.equal(editor.document.getText(), testContent); + const cancelSrc = new CancellationTokenSource(); + const completionPromise = completionProvider.provideCompletionItems(editor.document, new Position(2, 10), cancelSrc.token); + if (completionPromise) { + assert.equal(1, 2, `Invalid completion at property value`); + } + return Promise.resolve(); + }); + }); + }); + test('Expand abbreviation in completion list (CSS)', () => { const abbreviation = 'm10'; const expandedText = 'margin: 10px;'; @@ -430,8 +502,20 @@ suite('Tests for Expand Abbreviations (CSS)', () => { const completionPromise2 = completionProvider.provideCompletionItems(editor.document, new Position(5, 5), cancelSrc.token); const completionPromise3 = completionProvider.provideCompletionItems(editor.document, new Position(11, 4), cancelSrc.token); const completionPromise4 = completionProvider.provideCompletionItems(editor.document, new Position(14, 5), cancelSrc.token); + if (!completionPromise1) { + assert.equal(1, 2, `Problem with expanding padding abbreviations at line 3 col 4`); + } + if (!completionPromise2) { + assert.equal(1, 2, `Problem with expanding padding abbreviations at line 5 col 5`); + } + if (!completionPromise3) { + assert.equal(1, 2, `Problem with expanding padding abbreviations at line 11 col 4`); + } + if (!completionPromise4) { + assert.equal(1, 2, `Problem with expanding padding abbreviations at line 14 col 5`); + } + if (!completionPromise1 || !completionPromise2 || !completionPromise3 || !completionPromise4) { - assert.equal(1, 2, `Problem with expanding padding abbreviations`); return Promise.resolve(); } @@ -515,6 +599,21 @@ m10 }); + test('Skip when typing property values when there is a nested rule in the next line (SCSS)', () => { + return withRandomFileEditor(scssContents, 'scss', (editor, doc) => { + editor.selection = new Selection(19, 10, 19, 10); + return expandEmmetAbbreviation(null).then(() => { + assert.equal(editor.document.getText(), scssContents); + const cancelSrc = new CancellationTokenSource(); + const completionPromise = completionProvider.provideCompletionItems(editor.document, new Position(19, 10), cancelSrc.token); + if (completionPromise) { + assert.equal(1, 2, `Invalid completion at property value`); + } + return Promise.resolve(); + }); + }); +}); + suite('Tests for Wrap with Abbreviations', () => { teardown(closeAllEditors); diff --git a/extensions/emmet/src/typings/EmmetNode.d.ts b/extensions/emmet/src/typings/EmmetNode.d.ts index 476af0ed9312e95fc23d878e23cb30462f525f0b..478241f0d8a3040406834c33377c46d3cadde9b3 100644 --- a/extensions/emmet/src/typings/EmmetNode.d.ts +++ b/extensions/emmet/src/typings/EmmetNode.d.ts @@ -68,9 +68,10 @@ declare module 'EmmetNode' { export interface Property extends CssNode { valueToken: Token - separator: Token + separator: string parent: Rule terminatorToken: Token + separatorToken: Token value: string }