diff --git a/extensions/emmet/src/abbreviationActions.ts b/extensions/emmet/src/abbreviationActions.ts index fae02e600cdb01105f3c027588564b8817b533ab..50951f4333c4bd5130110fcc9dc5974852f104df 100644 --- a/extensions/emmet/src/abbreviationActions.ts +++ b/extensions/emmet/src/abbreviationActions.ts @@ -272,7 +272,7 @@ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocumen start = lastChildBeforePosition.end; lastChildBeforePosition = lastChildBeforePosition.nextSibling; } - let textToBackTrack = document.getText(new vscode.Range(start, abbreviationRange.start)); + let textToBackTrack = document.getText(new vscode.Range(start.line, start.character, abbreviationRange.start.line, abbreviationRange.start.character)); // Worse case scenario is when cursor is inside a big chunk of text which needs to backtracked // Backtrack only 500 offsets to ensure we dont waste time doing this @@ -280,6 +280,10 @@ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocumen textToBackTrack = textToBackTrack.substr(textToBackTrack.length - 500); } + if (!textToBackTrack.trim()) { + return true; + } + let valid = true; let foundSpace = false; // If < is found before finding whitespace, then its valid abbreviation. Eg: { return testExpandAbbreviation('html', new Selection(3, 23, 3, 23), 'img', '\"\"'); }); + test('Expand snippets in completion list (HTML)', () => { + return testHtmlCompletionProvider(new Selection(3, 23, 3, 23), 'img', '\"\"'); + }); + test('Expand abbreviation (HTML)', () => { return testExpandAbbreviation('html', new Selection(5, 25, 5, 25), 'ul>li', ''); }); + test('Expand abbreviation in completion list (HTML)', () => { + return testHtmlCompletionProvider(new Selection(5, 25, 5, 25), 'ul>li', ''); + }); + test('Expand text that is neither an abbreviation nor a snippet to tags (HTML)', () => { return testExpandAbbreviation('html', new Selection(4, 20, 4, 27), 'hithere', ''); }); + test('Do not Expand text that is neither an abbreviation nor a snippet to tags in completion list (HTML)', () => { + return testHtmlCompletionProvider(new Selection(4, 20, 4, 27), 'hithere', '', true); + }); + test('Expand abbreviation with repeaters (HTML)', () => { return testExpandAbbreviation('html', new Selection(6, 27, 6, 27), 'ul>li*2', ''); }); + test('Expand abbreviation with repeaters in completion list (HTML)', () => { + return testHtmlCompletionProvider(new Selection(6, 27, 6, 27), 'ul>li*2', ''); + }); + test('Expand abbreviation with numbered repeaters (HTML)', () => { return testExpandAbbreviation('html', new Selection(7, 33, 7, 33), 'ul>li.item$*2', ''); }); + test('Expand abbreviation with numbered repeaters in completion list (HTML)', () => { + return testHtmlCompletionProvider( new Selection(7, 33, 7, 33), 'ul>li.item$*2', ''); + }); + test('Expand abbreviation with numbered repeaters with offset (HTML)', () => { return testExpandAbbreviation('html', new Selection(8, 36, 8, 36), 'ul>li.item$@44*2', ''); }); + test('Expand abbreviation with numbered repeaters with offset in completion list (HTML)', () => { + return testHtmlCompletionProvider( new Selection(8, 36, 8, 36), 'ul>li.item$@44*2', ''); + }); + test('Expand abbreviation with numbered repeaters in groups (HTML)', () => { return testExpandAbbreviation('html', new Selection(17, 16, 17, 16), '(ul>li.item$)*2', '\n\t'); }); + test('Expand abbreviation with numbered repeaters in groups in completion list (HTML)', () => { + return testHtmlCompletionProvider(new Selection(17, 16, 17, 16), '(ul>li.item$)*2', '\n'); + }); + test('Expand abbreviation with numbered repeaters in groups with sibling in the end (HTML)', () => { return testExpandAbbreviation('html', new Selection(18, 21, 18, 21), '(ul>li.item$)*2+span', '\n\t\n\t'); }); + test('Expand abbreviation with numbered repeaters in groups with sibling in the end in completion list (HTML)', () => { + return testHtmlCompletionProvider(new Selection(18, 21, 18, 21), '(ul>li.item$)*2+span', '\n\n'); + }); + test('Expand abbreviation with nested groups (HTML)', () => { return testExpandAbbreviation('html', new Selection(19, 19, 19, 19), '(div>dl>(dt+dd)*2)', '
\n\t\t
\n\t\t\t
\n\t\t\t
\n\t\t\t
\n\t\t\t
\n\t\t
\n\t
'); }); + test('Expand abbreviation with nested groups in completion list (HTML)', () => { + return testHtmlCompletionProvider(new Selection(19, 19, 19, 19), '(div>dl>(dt+dd)*2)', '
\n\t
\n\t\t
\n\t\t
\n\t\t
\n\t\t
\n\t
\n
'); + }); + test('Expand tag that is opened, but not closed (HTML)', () => { return testExpandAbbreviation('html', new Selection(9, 6, 9, 6), ''); }); + test('Do not Expand tag that is opened, but not closed in completion list (HTML)', () => { + return testHtmlCompletionProvider(new Selection(9, 6, 9, 6), '', true); + }); + test('No expanding text inside open tag (HTML)', () => { return testExpandAbbreviation('html', new Selection(2, 4, 2, 4), '', '', true); }); + test('No expanding text inside open tag in completion list (HTML)', () => { + return testHtmlCompletionProvider(new Selection(2, 4, 2, 4), '', '', true); + }); + test('No expanding text inside open tag when there is no closing tag (HTML)', () => { return testExpandAbbreviation('html', new Selection(9, 8, 9, 8), '', '', true); }); + test('No expanding text inside open tag when there is no closing tag in completion list (HTML)', () => { + return testHtmlCompletionProvider(new Selection(9, 8, 9, 8), '', '', true); + }); + test('Expand css when inside style tag (HTML)', () => { return withRandomFileEditor(htmlContents, 'html', (editor, doc) => { editor.selection = new Selection(13, 3, 13, 6); @@ -180,6 +230,33 @@ suite('Tests for Expand Abbreviations (HTML)', () => { }); }); + test('Expand css when inside style tag in completion list (HTML)', () => { + const abbreviation = 'm10'; + const expandedText = 'margin: 10px;'; + + return withRandomFileEditor(htmlContents, 'html', (editor, doc) => { + editor.selection = new Selection(13, 3, 13, 6); + const cancelSrc = new CancellationTokenSource(); + const completionPromise = completionProvider.provideCompletionItems(editor.document, editor.selection.active, cancelSrc.token); + if (!completionPromise) { + assert.equal(1, 2, `Problem with expanding m10`); + return Promise.resolve(); + } + + return completionPromise.then((completionList: CompletionList) => { + if (!completionList.items || !completionList.items.length) { + assert.equal(1, 2, `Problem with expanding m10`); + return Promise.resolve(); + } + const emmetCompletionItem = completionList.items[0]; + assert.equal(emmetCompletionItem.label, expandedText, `Label of completion item doesnt match.`); + assert.equal((emmetCompletionItem.documentation || '').replace(/\|/g, ''), expandedText, `Docs of completion item doesnt match.`); + assert.equal(emmetCompletionItem.filterText, abbreviation, `FilterText of completion item doesnt match.`) + return Promise.resolve(); + }); + }); + }); + test('No expanding when html is excluded in the settings', () => { return workspace.getConfiguration('emmet').update('excludeLanguages', ['html']).then(() => { return testExpandAbbreviation('html', new Selection(9, 6, 9, 6), '', '', true).then(() => { @@ -188,6 +265,14 @@ suite('Tests for Expand Abbreviations (HTML)', () => { }); }); + test('No expanding when html is excluded in the settings in completion list', () => { + return workspace.getConfiguration('emmet').update('excludeLanguages', ['html']).then(() => { + return testHtmlCompletionProvider(new Selection(9, 6, 9, 6), '', '', true).then(() => { + return workspace.getConfiguration('emmet').update('excludeLanguages', []); + }); + }); + }); + test('No expanding when php (mapped syntax) is excluded in the settings', () => { return workspace.getConfiguration('emmet').update('excludeLanguages', ['php']).then(() => { return testExpandAbbreviation('php', new Selection(9, 6, 9, 6), '', '', true).then(() => { @@ -212,6 +297,39 @@ suite('Tests for Expand Abbreviations (CSS)', () => { }); }); + test('Expand abbreviation in completion list (CSS)', () => { + const abbreviation = 'm10'; + const expandedText = 'margin: 10px;'; + + return withRandomFileEditor(cssContents, 'css', (editor, doc) => { + editor.selection = new Selection(3, 1, 3, 4); + const cancelSrc = new CancellationTokenSource(); + const completionPromise1 = completionProvider.provideCompletionItems(editor.document, new Position(3,4), cancelSrc.token); + const completionPromise2 = completionProvider.provideCompletionItems(editor.document, new Position(5,4), cancelSrc.token); + if (!completionPromise1 || !completionPromise2) { + assert.equal(1, 2, `Problem with expanding m10`); + return Promise.resolve(); + } + + const callBack = (completionList: CompletionList) => { + if (!completionList.items || !completionList.items.length) { + assert.equal(1, 2, `Problem with expanding m10`); + return; + } + const emmetCompletionItem = completionList.items[0]; + assert.equal(emmetCompletionItem.label, expandedText, `Label of completion item doesnt match.`); + assert.equal((emmetCompletionItem.documentation || '').replace(/\|/g, ''), expandedText, `Docs of completion item doesnt match.`); + assert.equal(emmetCompletionItem.filterText, abbreviation, `FilterText of completion item doesnt match.`); + }; + + return Promise.all([completionPromise1, completionPromise2]).then(([result1, result2]) => { + callBack(result1); + callBack(result2); + return Promise.resolve(); + }); + }); + }); + test('Expand abbreviation (SCSS)', () => { return withRandomFileEditor(scssContents, 'scss', (editor, doc) => { editor.selections = [ @@ -227,7 +345,43 @@ suite('Tests for Expand Abbreviations (CSS)', () => { }); }); - test('Invalid locations for abbreviations in css', () => { + test('Expand abbreviation in completion list (SCSS)', () => { + + return withRandomFileEditor(scssContents, 'scss', (editor, doc) => { + editor.selection = new Selection(3, 4, 3, 4); + const cancelSrc = new CancellationTokenSource(); + const completionPromise1 = completionProvider.provideCompletionItems(editor.document, new Position(3, 4), cancelSrc.token); + 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 || !completionPromise2 || !completionPromise3 || !completionPromise4) { + assert.equal(1, 2, `Problem with expanding padding abbreviations`); + return Promise.resolve(); + } + + const callBack = (completionList: CompletionList, abbreviation, expandedText) => { + if (!completionList.items || !completionList.items.length) { + assert.equal(1, 2, `Problem with expanding m10`); + return; + } + const emmetCompletionItem = completionList.items[0]; + assert.equal(emmetCompletionItem.label, expandedText, `Label of completion item doesnt match.`); + assert.equal((emmetCompletionItem.documentation || '').replace(/\|/g, ''), expandedText, `Docs of completion item doesnt match.`); + assert.equal(emmetCompletionItem.filterText, abbreviation, `FilterText of completion item doesnt match.`); + }; + + return Promise.all([completionPromise1, completionPromise2, completionPromise3, completionPromise4]).then(([result1, result2, result3, result4]) => { + callBack(result1, 'p10', 'padding: 10px;'); + callBack(result2, 'p20', 'padding: 20px;'); + callBack(result3, 'p10', 'padding: 30px;'); + callBack(result4, 'p20', 'padding: 40px;'); + return Promise.resolve(); + }); + }); + }); + + + test('Invalid locations for abbreviations in scss', () => { const scssContentsNoExpand = ` m10 .boo { @@ -249,6 +403,36 @@ m10 }); }); }); + + test('Invalid locations for abbreviations in scss in completion list', () => { + const scssContentsNoExpand = ` +m10 + .boo { + margin: 10px; + .hoo { + background: + } + } + ` + + return withRandomFileEditor(scssContentsNoExpand, 'scss', (editor, doc) => { + editor.selection = new Selection(1, 3, 1, 3); // outside rule + const cancelSrc = new CancellationTokenSource(); + let completionPromise = completionProvider.provideCompletionItems(editor.document, editor.selection.active, cancelSrc.token); + if (completionPromise) { + assert.equal(1, 2, `m10 gets expanded in invalid location`); + } + + editor.selection = new Selection(5, 15, 5, 15); // in the value part of property value + completionPromise = completionProvider.provideCompletionItems(editor.document, editor.selection.active, cancelSrc.token); + if (completionPromise) { + assert.equal(1, 2, `m10 gets expanded in invalid location`); + } + return Promise.resolve(); + + }); + }); + }); suite('Tests for Wrap with Abbreviations', () => { @@ -429,6 +613,33 @@ function testExpandAbbreviation(syntax: string, selection: Selection, abbreviati }); } +function testHtmlCompletionProvider(selection: Selection, abbreviation: string, expandedText: string, shouldFail?: boolean): Thenable { + return withRandomFileEditor(htmlContents, 'html', (editor, doc) => { + editor.selection = selection; + const cancelSrc = new CancellationTokenSource(); + const completionPromise = completionProvider.provideCompletionItems(editor.document, editor.selection.active, cancelSrc.token); + if (!completionPromise) { + if (!shouldFail) { + assert.equal(1, 2, `Problem with expanding ${abbreviation} to ${expandedText}`); + } + return Promise.resolve(); + } + + return completionPromise.then((completionList: CompletionList) => { + if (!completionList.items || !completionList.items.length) { + if (!shouldFail) { + assert.equal(1, 2, `Problem with expanding ${abbreviation} to ${expandedText}`); + } + return Promise.resolve(); + } + const emmetCompletionItem = completionList.items[0]; + assert.equal(emmetCompletionItem.label, abbreviation, `Label of completion item doesnt match.`); + assert.equal((emmetCompletionItem.documentation || '').replace(/\|/g, ''), expandedText, `Docs of completion item doesnt match.`); + return Promise.resolve(); + }); + }); +} + function testWrapWithAbbreviation(selections: Selection[], abbreviation: string, expectedContents: string): Thenable { return withRandomFileEditor(htmlContentsForWrapTests, 'html', (editor, doc) => { editor.selections = selections;