diff --git a/extensions/emmet/src/test/updateImageSize.test.ts b/extensions/emmet/src/test/updateImageSize.test.ts new file mode 100644 index 00000000000..2a93c777bf9 --- /dev/null +++ b/extensions/emmet/src/test/updateImageSize.test.ts @@ -0,0 +1,150 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { Selection, commands } from 'vscode'; +import { withRandomFileEditor, closeAllEditors } from './testUtils'; +import * as path from 'path'; + +suite('Tests for Emmet actions on html tags', () => { + teardown(closeAllEditors); + const filePath = path.join(__dirname, '../../../../resources/linux/code.png'); + + test('update image css with multiple cursors in css file', () => { + const cssContents = ` + .one { + margin: 10px; + padding: 10px; + background-image: url(https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png); + } + .two { + background-image: url(https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png); + height: 42px; + } + .three { + background-image: url(${filePath}); + width: 42px; + } + `; + const expectedContents = ` + .one { + margin: 10px; + padding: 10px; + background-image: url(https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png); + width: 32px; + height: 32px; + } + .two { + background-image: url(https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png); + width: 32px; + height: 32px; + } + .three { + background-image: url(${filePath}); + height: 1024px; + width: 1024px; + } + `; + return withRandomFileEditor(cssContents, 'css', (editor, doc) => { + editor.selections = [ + new Selection(4, 50, 4, 50), + new Selection(7, 50, 7, 50), + new Selection(11, 50, 11, 50) + ]; + + return commands.executeCommand('emmet.updateImageSize').then(() => { + assert.equal(doc.getText(), expectedContents); + return Promise.resolve(); + }); + }); + }); + + test('update image size in css in html file with multiple cursors', () => { + const htmlWithCssContents = ` + + + + `; + const expectedContents = ` + + + + `; + return withRandomFileEditor(htmlWithCssContents, 'html', (editor, doc) => { + editor.selections = [ + new Selection(6, 50, 6, 50), + new Selection(9, 50, 9, 50), + new Selection(13, 50, 13, 50) + ]; + + return commands.executeCommand('emmet.updateImageSize').then(() => { + assert.equal(doc.getText(), expectedContents); + return Promise.resolve(); + }); + }); + }); + + test('update image size in img tag in html file with multiple cursors', () => { + const htmlwithimgtag = ` + + + + + + `; + const expectedContents = ` + + + + + + `; + return withRandomFileEditor(htmlwithimgtag, 'html', (editor, doc) => { + editor.selections = [ + new Selection(2, 50, 2, 50), + new Selection(3, 50, 3, 50), + new Selection(4, 50, 4, 50) + ]; + + return commands.executeCommand('emmet.updateImageSize').then(() => { + assert.equal(doc.getText(), expectedContents); + return Promise.resolve(); + }); + }); + }); + +}); diff --git a/extensions/emmet/src/updateImageSize.ts b/extensions/emmet/src/updateImageSize.ts index 46edeee044d..11471c888e9 100644 --- a/extensions/emmet/src/updateImageSize.ts +++ b/extensions/emmet/src/updateImageSize.ts @@ -27,79 +27,92 @@ export function updateImageSize() { return; } - if (!isStyleSheet(editor.document.languageId)) { - return updateImageSizeHTML(editor); - } else { - return updateImageSizeCSSFile(editor); - } + let allUpdatesPromise = editor.selections.reverse().map(selection => { + let position = selection.isReversed ? selection.active : selection.anchor; + if (!isStyleSheet(editor.document.languageId)) { + return updateImageSizeHTML(editor, position); + } else { + return updateImageSizeCSSFile(editor, position); + } + }); + + return Promise.all(allUpdatesPromise).then((updates) => { + return editor.edit(builder => { + updates.forEach(update => { + update.forEach(([rangeToReplace, textToReplace]) => { + builder.replace(rangeToReplace, textToReplace); + }); + }); + }); + }); } /** * Updates image size of context tag of HTML model */ -function updateImageSizeHTML(editor: TextEditor) { - const src = getImageSrcHTML(getImageHTMLNode(editor)); +function updateImageSizeHTML(editor: TextEditor, position: Position): Promise<[Range, string][]> { + const src = getImageSrcHTML(getImageHTMLNode(editor, position)); if (!src) { - return updateImageSizeStyleTag(editor); + return updateImageSizeStyleTag(editor, position); } - locateFile(path.dirname(editor.document.fileName), src) + return locateFile(path.dirname(editor.document.fileName), src) .then(getImageSize) .then((size: any) => { // since this action is asynchronous, we have to ensure that editor wasn’t // changed and user didn’t moved caret outside node - const img = getImageHTMLNode(editor); + const img = getImageHTMLNode(editor, position); if (getImageSrcHTML(img) === src) { - updateHTMLTag(editor, img, size.width, size.height); + return updateHTMLTag(editor, img, size.width, size.height); } }) - .catch(err => console.warn('Error while updating image size:', err)); + .catch(err => { console.warn('Error while updating image size:', err); return []; }); } -function updateImageSizeStyleTag(editor: TextEditor) { +function updateImageSizeStyleTag(editor: TextEditor, position: Position): Promise<[Range, string][]> { let getPropertyInsiderStyleTag = (editor) => { const rootNode = parseDocument(editor.document); - const currentNode = getNode(rootNode, editor.selection.active); + const currentNode = getNode(rootNode, position); if (currentNode && currentNode.name === 'style' - && currentNode.open.end.isBefore(editor.selection.active) - && currentNode.close.start.isAfter(editor.selection.active)) { + && currentNode.open.end.isBefore(position) + && currentNode.close.start.isAfter(position)) { let buffer = new DocumentStreamReader(editor.document, currentNode.start, new Range(currentNode.start, currentNode.end)); let rootNode = parseStylesheet(buffer); - const node = getNode(rootNode, editor.selection.active); + const node = getNode(rootNode, position); return (node && node.type === 'property') ? node : null; } }; - return updateImageSizeCSS(editor, getPropertyInsiderStyleTag); + return updateImageSizeCSS(editor, position, getPropertyInsiderStyleTag); } -function updateImageSizeCSSFile(editor: TextEditor) { - return updateImageSizeCSS(editor, getImageCSSNode); +function updateImageSizeCSSFile(editor: TextEditor, position: Position): Promise<[Range, string][]> { + return updateImageSizeCSS(editor, position, getImageCSSNode); } /** * Updates image size of context rule of stylesheet model */ -function updateImageSizeCSS(editor: TextEditor, fetchNode: (editor) => Property) { +function updateImageSizeCSS(editor: TextEditor, position: Position, fetchNode: (editor, position) => Property): Promise<[Range, string][]> { - const src = getImageSrcCSS(fetchNode(editor), editor.selection.active); + const src = getImageSrcCSS(fetchNode(editor, position), position); if (!src) { return Promise.reject(new Error('No valid image source')); } - locateFile(path.dirname(editor.document.fileName), src) + return locateFile(path.dirname(editor.document.fileName), src) .then(getImageSize) .then((size: any) => { // since this action is asynchronous, we have to ensure that editor wasn’t // changed and user didn’t moved caret outside node - const prop = fetchNode(editor); - if (getImageSrcCSS(prop, editor.selection.active) === src) { - updateCSSNode(editor, prop, size.width, size.height); + const prop = fetchNode(editor, position); + if (getImageSrcCSS(prop, position) === src) { + return updateCSSNode(editor, prop, size.width, size.height); } }) - .catch(err => console.warn('Error while updating image size:', err)); + .catch(err => { console.warn('Error while updating image size:', err); return []; }); } /** @@ -108,9 +121,9 @@ function updateImageSizeCSS(editor: TextEditor, fetchNode: (editor) => Property) * @param {TextEditor} editor * @return {HtmlNode} */ -function getImageHTMLNode(editor: TextEditor): HtmlNode { +function getImageHTMLNode(editor: TextEditor, position: Position): HtmlNode { const rootNode = parseDocument(editor.document); - const node = getNode(rootNode, editor.selection.active, true); + const node = getNode(rootNode, position, true); return node && node.name.toLowerCase() === 'img' ? node : null; } @@ -121,9 +134,9 @@ function getImageHTMLNode(editor: TextEditor): HtmlNode { * @param {TextEditor} editor * @return {Property} */ -function getImageCSSNode(editor: TextEditor): Property { +function getImageCSSNode(editor: TextEditor, position: Position): Property { const rootNode = parseDocument(editor.document); - const node = getNode(rootNode, editor.selection.active, true); + const node = getNode(rootNode, position, true); return node && node.type === 'property' ? node : null; } @@ -135,7 +148,6 @@ function getImageCSSNode(editor: TextEditor): Property { function getImageSrcHTML(node: HtmlNode): string { const srcAttr = getAttribute(node, 'src'); if (!srcAttr) { - console.warn('No "src" attribute in', node && node.open); return; } @@ -173,7 +185,7 @@ function getImageSrcCSS(node: Property, position: Position): string { * @param {number} width * @param {number} height */ -function updateHTMLTag(editor: TextEditor, node: HtmlNode, width: number, height: number) { +function updateHTMLTag(editor: TextEditor, node: HtmlNode, width: number, height: number): [Range, string][] { const srcAttr = getAttribute(node, 'src'); const widthAttr = getAttribute(node, 'width'); const heightAttr = getAttribute(node, 'height'); @@ -197,11 +209,7 @@ function updateHTMLTag(editor: TextEditor, node: HtmlNode, width: number, height edits.push([new Range(endOfAttributes, endOfAttributes), textToAdd]); } - return editor.edit(builder => { - edits.forEach(([rangeToReplace, textToReplace]) => { - builder.replace(rangeToReplace, textToReplace); - }); - }); + return edits; } /** @@ -211,7 +219,7 @@ function updateHTMLTag(editor: TextEditor, node: HtmlNode, width: number, height * @param {number} width * @param {number} height */ -function updateCSSNode(editor: TextEditor, srcProp: Property, width: number, height: number) { +function updateCSSNode(editor: TextEditor, srcProp: Property, width: number, height: number): [Range, string][] { const rule = srcProp.parent; const widthProp = getCssPropertyFromRule(rule, 'width'); const heightProp = getCssPropertyFromRule(rule, 'height'); @@ -240,11 +248,7 @@ function updateCSSNode(editor: TextEditor, srcProp: Property, width: number, hei edits.push([new Range(srcProp.end, srcProp.end), textToAdd]); } - return editor.edit(builder => { - edits.forEach(([rangeToReplace, textToReplace]) => { - builder.replace(rangeToReplace, textToReplace); - }); - }); + return edits; } /**