diff --git a/README.md b/README.md
index b8f15cd..251b229 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,9 @@
[](https://travis-ci.org/Microsoft/vscode-htmltagwrap)
## What is it
-Wraps your selection in HTML tags. Can wrap an inline selection or a selection of multiple lines.
+Wraps your selection in HTML tags. Can wrap an inline selection, a selection of multiple lines or mutiple selections.
-To use, select a chunk of code and press **"Alt + W" ("Option + W" for Mac).**
+To use, select one or many chunks of code and press **"Alt + W" ("Option + W" for Mac).**

@@ -19,10 +19,12 @@ This extension works best in files that either use tabs or spaces for indentatio
I welcome pull requests. Please report an issue on GitHub if you have trouble.
## Updates
+### 0.0.4
+* Support for multiple selections
+
### 0.0.3
* Spaces and tabs for indentation are now both supported.
- Special thanks to @gdziadkiewicz for his PR #4, which also introduced tests for development!
## Future features being explored
- #5 Once a user hits spacebar, then the second multi-cursor is lost so the user can write attributes like classes or styles. (Idea courtesy of Ruben Rios)
-- Should we support `getSelections()` allowing multiple selections to be wrapped?
diff --git a/package.json b/package.json
index 1088711..007c152 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
"url" : "https://github.com/Microsoft/vscode-htmltagwrap"
},
"description": "Wraps selected code with HTML tags",
- "version": "0.0.3",
+ "version": "0.0.4",
"publisher": "bradgashler",
"engines": {
"vscode": "^1.0.0"
diff --git a/src/extension.ts b/src/extension.ts
index d9a413c..dcaa4d0 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -18,7 +18,7 @@ export function activate() {
// Use the console to output diagnostic information (console.log) and errors (console.error)
// This line of code will only be executed once when your extension is activated
- console.log('Congratulations, your extension "copythis" is now active!');
+ console.log('Congratulations, your extension "htmlTagWrap" is now active!');
// The command has been defined in the package.json file
// Now provide the implementation of the command with registerCommand
@@ -26,38 +26,31 @@ export function activate() {
vscode.commands.registerCommand('extension.htmlTagWrap', () => {
// The code you place here will be executed every time your command is executed
- var editor = vscode.window.activeTextEditor;
- if (editor != undefined) {
+ const editor = vscode.window.activeTextEditor;
+ let tabSizeSpace = getTabString(editor);
- var selection = editor.selection;
- var selectedText = editor.document.getText(selection);
+ if(editor == null) {
+ return;
+ }
+
+ editor.edit((editBuilder) => {
- var firstIndex = 1;
- var lastIndex = selectedText.length;
+ const selections = editor.selections;
+
+ for(const selection of selections) {
- console.log('selection is: ' + selectedText);
- console.log('length is: ' + lastIndex);
- console.log('selection.start.character: ' + selection.start.character);
- console.log('selection.end.character: ' + selection.end.character);
+ const selectionStart = selection.start;
+ const selectionEnd = selection.end;
+
+ if(selectionEnd.line !== selectionStart.line) {
+ // Wrap it as a block
+ var selectionStart_spaces = editor.document.lineAt(selectionStart.line).text.substring(0, selectionStart.character);
+ //console.log('selectionStart_spaces = ' + selectionStart_spaces);
+ //console.log('tabsizeSpace =' + tabSizeSpace);
- var selectionStart = selection.start;
- var selectionEnd = selection.end;
-
- if (selectionEnd.line > selectionStart.line) {
- //Wrap it as a block
- var lineAbove = selectionStart.line - 1;
- var lineBelow = selectionEnd.line + 1;
-
- //console.log('tabSize = ' + tabSize);
- let tabSizeSpace = getTabString(editor);
- var selectionStart_spaces = editor.document.lineAt(selectionStart.line).text.substring(0, selectionStart.character);
- //console.log('selectionStart_spaces = ' + selectionStart_spaces);
- //console.log('tabsizeSpace =' + tabSizeSpace);
-
- editor.edit((editBuilder) => {
- // Modify last line of selection
editBuilder.insert(new vscode.Position(selectionEnd.line, selectionEnd.character), '\n' + selectionStart_spaces + '
');
editBuilder.insert(new vscode.Position(selectionEnd.line, 0), tabSizeSpace);
+
console.log('End line done. Line #: ' + selectionEnd.line);
for (let lineNumber = selectionEnd.line - 1; lineNumber > selectionStart.line; lineNumber--) {
@@ -65,41 +58,57 @@ export function activate() {
editBuilder.insert(new vscode.Position(lineNumber, 0), tabSizeSpace);
}
- // Modify firs line of selection
+ // Modify first line of selection
editBuilder.insert(new vscode.Position(selectionStart.line, selectionStart.character), '\n' + selectionStart_spaces + tabSizeSpace);
console.log('Start Line done. Line #: ' + selectionStart.line);
- }).then(() => {
- console.log('Edit applied!');
-
- var bottomTagLine = lineBelow + 1;
- var firstTagSelectionSelection: vscode.Selection = new vscode.Selection(selectionStart.line, selectionStart.character + 1, selectionStart.line, selectionStart.character + 2);
- var lastTagSelectionSelection: vscode.Selection = new vscode.Selection(bottomTagLine, selectionStart.character + 2, bottomTagLine, selectionStart.character + 3);
- var tagSelections: vscode.Selection[] = [firstTagSelectionSelection, lastTagSelectionSelection];
-
- editor.selections = tagSelections;
- }, (err) => {
- console.log('Edit rejected!');
- console.error(err);
- });
+ }
+ else {
+ //Wrap it inline
+ editBuilder.insert(new vscode.Position(selectionEnd.line, selectionStart.character), '
');
+ editBuilder.insert(new vscode.Position(selectionEnd.line, selectionEnd.character), '
');
+ }
}
- else {
- //Wrap it inline
- editor.edit((editBuilder) => {
- editBuilder.insert(new vscode.Position(selectionEnd.line, selectionEnd.character), '');
- editBuilder.insert(new vscode.Position(selectionEnd.line, selectionStart.character), '');
- }).then(() => {
- console.log('Edit applied!');
-
- var firstTagSelectionSelection: vscode.Selection = new vscode.Selection(selectionStart.line, selectionStart.character + 1, selectionStart.line, selectionStart.character + 2);
- var lastTagSelectionSelection: vscode.Selection = new vscode.Selection(selectionEnd.line, selectionEnd.character + 3 + 2, selectionEnd.line, selectionEnd.character + 3 + 3);
- var tagSelections: vscode.Selection[] = [firstTagSelectionSelection, lastTagSelectionSelection];
-
- editor.selections = tagSelections;
- }, (err) => {
- console.log('Edit rejected!');
- console.error(err);
- });
+ }).then(() => {
+ console.log('Edit applied!');
+
+ // Need to fetch selections again as they are no longer accurate (since the new tags were inserted)
+ const selections = editor.selections;
+ const toSelect: Array = new Array();
+
+ for(let selection of selections) {
+
+ // Careful : the selection starts at the beginning of the text but ends *after* the closing
tag
+ if(selection.end.line !== selection.start.line) {
+ // block
+ let lineAbove = selection.start.line - 1;
+ let lineBelow = selection.end.line;
+ let startPosition = selection.start.character - tabSizeSpace.length + 1;
+ let endPosition = selection.end.character - 2;
+
+ toSelect.push(new vscode.Selection(lineAbove, startPosition, lineAbove, startPosition + 1));
+ toSelect.push(new vscode.Selection(lineBelow, endPosition, lineBelow, endPosition + 1));
+ }
+ else {
+ // same line, just get to the p by navigating backwards
+ let startPosition = selection.start.character - 2;
+ let endPosition = selection.end.character - 2;
+
+ // When dealing with an empty selection, both the start and end position end up being *after* the closing tag
+ // backtrack to account for that
+ if(selection.start.character === selection.end.character) {
+ startPosition -= 4;
+ }
+
+ toSelect.push(new vscode.Selection(selection.start.line, startPosition, selection.start.line, startPosition + 1))
+ toSelect.push(new vscode.Selection(selection.end.line, endPosition, selection.end.line, endPosition + 1))
+ }
+
+ editor.selections = toSelect;
}
- };
+
+ }, (err) => {
+ console.log('Edit rejected!');
+ console.error(err);
+ });
});
}
\ No newline at end of file
diff --git a/test/extension.test.ts b/test/extension.test.ts
index 18a1470..97819db 100644
--- a/test/extension.test.ts
+++ b/test/extension.test.ts
@@ -8,12 +8,22 @@ import {workspace, window, Selection, Position, commands, extensions, Uri, TextE
import {copySync, CopyOptions, emptyDir} from 'fs-extra';
import * as extension from '../src/extension';
+// A cursor selection is a StartPosition : EndPosition couple
+type CursorSelection = [Position, Position];
+
extension.activate();
let extensionID = 'bradgashler.htmltagwrap';
let samplesFolder = extensions.getExtension(extensionID).extensionPath + '/test/sampleFiles/';
let tempFolder = samplesFolder + 'temp/';
-function parametrizedTest(startFilePath: string, expectedResultFilePath: string, selectionStart: Position, selectionEnd: Position, failMessage: string) {
+function parametrizedSingleSelectionTest(startFilePath: string, expectedResultFilePath: string, selectionStart: Position, selectionEnd: Position, failMessage: string) {
+ const selection:CursorSelection = [selectionStart, selectionEnd];
+ const selections: Array = [selection];
+
+ return parametrizedMultiSelectionTest(startFilePath, expectedResultFilePath, selections, failMessage);
+}
+
+function parametrizedMultiSelectionTest(startFilePath: string, expectedResultFilePath: string, selections: Array, failMessage: string) {
let result: string;
let expectedResult: string;
let editor: TextEditor;
@@ -25,7 +35,7 @@ function parametrizedTest(startFilePath: string, expectedResultFilePath: string,
}).then((_editor) => {
editor = _editor;
}).then(() => {
- editor.selection = new Selection(selectionStart, selectionEnd);
+ editor.selections = selections.map(s => new Selection(s[0], s[1]));
return commands.executeCommand('extension.htmlTagWrap').then(() => new Promise((f) => setTimeout(f, 500)));
}).then(() => {
result = editor.document.getText();
@@ -46,17 +56,63 @@ function parametrizedTest(startFilePath: string, expectedResultFilePath: string,
suite('Extension Tests', function () {
+
+ // Single selection tests
test('HTML with tabs block wrap test', function () {
- return parametrizedTest('tabFile.html', 'expectedTabBlockWrapFileResult.html', new Position(1, 1), new Position(6, 6), 'Tab using block wrap does not work');
+ return parametrizedSingleSelectionTest('tabFile.html', 'expectedTabBlockWrapFileResult.html', new Position(1, 1), new Position(6, 6), 'Tab using block wrap does not work');
});
test('HTML with spaces block wrap test', function () {
- return parametrizedTest('spaceFile.html', 'expectedSpaceBlockWrapFileResult.html', new Position(1, 4), new Position(7, 9), 'Space using block wrap does not work');
+ return parametrizedSingleSelectionTest('spaceFile.html', 'expectedSpaceBlockWrapFileResult.html', new Position(1, 4), new Position(7, 9), 'Space using block wrap does not work');
});
test('HTML with tabs line wrap test', function () {
- return parametrizedTest('tabFile.html', 'expectedTabLineWrapFileResult.html', new Position(2, 2), new Position(2, 11), 'Tab using line wrap does not work');
+ return parametrizedSingleSelectionTest('tabFile.html', 'expectedTabLineWrapFileResult.html', new Position(2, 2), new Position(2, 11), 'Tab using line wrap does not work');
});
test('HTML with spaces line wrap test', function () {
- return parametrizedTest('spaceFile.html', 'expectedSpaceLineWrapFileResult.html', new Position(2, 8), new Position(2, 17), 'Space using line wrap does not work');
+ return parametrizedSingleSelectionTest('spaceFile.html', 'expectedSpaceLineWrapFileResult.html', new Position(2, 8), new Position(2, 17), 'Space using line wrap does not work');
});
+ test('Empty selection line wrap test', function() {
+ return parametrizedSingleSelectionTest('emptyFile.html', 'expectedEmptyFileSingleCursorResult.html', new Position(0, 0), new Position(0, 0), 'Empty selection tag wrap does not work');
+ });
+
+ // Multiple selecetion tests
+ test('Multiple Empty selections line wrap test', function() {
+ const selections: Array = [
+ [new Position(1, 0), new Position(1, 0)],
+ [new Position(2, 0), new Position(2, 0)],
+ [new Position(3, 0), new Position(3, 0)]
+ ];
+ return parametrizedMultiSelectionTest('emptySelectionMultipleCursors.html', 'expectedEmptySelectionMultipleCursorsResult.html', selections, 'Empty selection tag wrap does not work with multiple selections');
+ });
+
+ test('Multiple selections block wrap test', function() {
+ const selections: Array = [
+ [new Position(1, 4), new Position(2, 17)],
+ [new Position(5, 0), new Position(6, 13)],
+ [new Position(10, 8), new Position(11, 15)]
+ ];
+ return parametrizedMultiSelectionTest('textBlocks.html', 'expectedMultiSelectionTextBlocksFileResult.html', selections, 'Multiple selections text block wrap does not work');
+ });
+
+ test('Multiple selections block wrap test', function() {
+ const selections: Array = [
+ [new Position(1, 4), new Position(2, 17)],
+ [new Position(5, 0), new Position(6, 13)],
+ [new Position(10, 8), new Position(11, 15)]
+ ];
+ return parametrizedMultiSelectionTest('textBlocks.html', 'expectedMultiSelectionTextBlocksFileResult.html', selections, 'Multiple selections text block wrap does not work');
+ });
+
+ test('Multiple selections mix block / text wrap test', function() {
+ const selections: Array = [
+ [new Position(1, 4), new Position(1, 21)],
+ [new Position(2, 4), new Position(2, 17)],
+ [new Position(5, 0), new Position(6, 13)],
+ [new Position(10, 8), new Position(10, 19)],
+ [new Position(11, 11), new Position(11, 15)]
+ ];
+ return parametrizedMultiSelectionTest('textBlocks.html', 'expectedMultiSelectionMixedLineBlockFileResult.html', selections, 'Multiple selections mixed (text and block) does not work');
+ });
+
+
teardown((done) => emptyDir(tempFolder, done));
});
\ No newline at end of file
diff --git a/test/sampleFiles/emptyFile.html b/test/sampleFiles/emptyFile.html
new file mode 100644
index 0000000..e69de29
diff --git a/test/sampleFiles/emptySelectionMultipleCursors.html b/test/sampleFiles/emptySelectionMultipleCursors.html
new file mode 100644
index 0000000..f6ab8d6
--- /dev/null
+++ b/test/sampleFiles/emptySelectionMultipleCursors.html
@@ -0,0 +1,5 @@
+content
+
+
+
+content
\ No newline at end of file
diff --git a/test/sampleFiles/expectedEmptyFileSingleCursorResult.html b/test/sampleFiles/expectedEmptyFileSingleCursorResult.html
new file mode 100644
index 0000000..540135a
--- /dev/null
+++ b/test/sampleFiles/expectedEmptyFileSingleCursorResult.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/test/sampleFiles/expectedEmptySelectionMultipleCursorsResult.html b/test/sampleFiles/expectedEmptySelectionMultipleCursorsResult.html
new file mode 100644
index 0000000..4f05fd7
--- /dev/null
+++ b/test/sampleFiles/expectedEmptySelectionMultipleCursorsResult.html
@@ -0,0 +1,5 @@
+content
+
+
+
+content
\ No newline at end of file
diff --git a/test/sampleFiles/expectedMultiSelectionMixedLineBlockFileResult.html b/test/sampleFiles/expectedMultiSelectionMixedLineBlockFileResult.html
new file mode 100644
index 0000000..2a41625
--- /dev/null
+++ b/test/sampleFiles/expectedMultiSelectionMixedLineBlockFileResult.html
@@ -0,0 +1,16 @@
+
+
this is the first
+
block of text
+
+
+
+ this is the second
+ block of text
+
+
+
+
+
this is the
third block
+ of
text
+
+
diff --git a/test/sampleFiles/expectedMultiSelectionTextBlocksFileResult.html b/test/sampleFiles/expectedMultiSelectionTextBlocksFileResult.html
new file mode 100644
index 0000000..e17a1f5
--- /dev/null
+++ b/test/sampleFiles/expectedMultiSelectionTextBlocksFileResult.html
@@ -0,0 +1,20 @@
+
+
+ this is the first
+ block of text
+
+
+
+
+ this is the second
+ block of text
+
+
+
+
+
+ this is the third block
+ of text
+
+
+
diff --git a/test/sampleFiles/textBlocks.html b/test/sampleFiles/textBlocks.html
new file mode 100644
index 0000000..08ebc13
--- /dev/null
+++ b/test/sampleFiles/textBlocks.html
@@ -0,0 +1,14 @@
+
+ this is the first
+ block of text
+
+
+this is the second
+block of text
+
+
+
+ this is the third block
+ of text
+
+