From fb690f08aa7e94d0f22e150b97aa1aeacb8ed194 Mon Sep 17 00:00:00 2001 From: Hugo Migneron Date: Mon, 31 Oct 2016 02:20:01 -0400 Subject: [PATCH 1/5] Allow tag wrap with multiple selections. This also addresses the issue of the wrong selection after wrapping an empty selection Progress on #6 --- src/extension.ts | 135 +++++++++++++++++++++++++---------------------- 1 file changed, 73 insertions(+), 62 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index d9a413c..b31ef78 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,33 @@ 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); + + if(editor == null) { + return; + } + + editor.edit((editBuilder) => { + + const selections = editor.selections; + + for(const selection of selections) { + + const selectionStart = selection.start; + const selectionEnd = selection.end; + + if(selectionEnd.line !== selectionStart.line) { + // Wrap it as a block + // TODO : This could be a bit better -- we should be finding the first non-whitespace character instead of taking + // the beginning of the selection. + var selectionStart_spaces = editor.document.lineAt(selectionStart.line).text.substring(0, selectionStart.character); + //console.log('selectionStart_spaces = ' + selectionStart_spaces); + //console.log('tabsizeSpace =' + tabSizeSpace); - var selection = editor.selection; - var selectedText = editor.document.getText(selection); - - var firstIndex = 1; - var lastIndex = selectedText.length; - - 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); - - 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 +60,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 From 04a09e124eddcf30a2eee546b5af9e53da0d5fb6 Mon Sep 17 00:00:00 2001 From: Hugo Migneron Date: Mon, 31 Oct 2016 10:30:24 -0400 Subject: [PATCH 2/5] Add test for empty selection bug (see #6) --- src/extension.ts | 2 -- test/extension.test.ts | 3 +++ test/sampleFiles/emptyFile.html | 0 test/sampleFiles/expectedEmptyFileResult.html | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 test/sampleFiles/emptyFile.html create mode 100644 test/sampleFiles/expectedEmptyFileResult.html diff --git a/src/extension.ts b/src/extension.ts index b31ef78..dcaa4d0 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -44,8 +44,6 @@ export function activate() { if(selectionEnd.line !== selectionStart.line) { // Wrap it as a block - // TODO : This could be a bit better -- we should be finding the first non-whitespace character instead of taking - // the beginning of the selection. var selectionStart_spaces = editor.document.lineAt(selectionStart.line).text.substring(0, selectionStart.character); //console.log('selectionStart_spaces = ' + selectionStart_spaces); //console.log('tabsizeSpace =' + tabSizeSpace); diff --git a/test/extension.test.ts b/test/extension.test.ts index 18a1470..af2fe79 100644 --- a/test/extension.test.ts +++ b/test/extension.test.ts @@ -58,5 +58,8 @@ suite('Extension Tests', function () { 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'); }); + test('Empty selection line wrap test', function() { + return parametrizedTest('emptyFile.html', 'expectedEmptyFileResult.html', new Position(0, 0), new Position(0, 0), 'Empty selection tag wrap 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/expectedEmptyFileResult.html b/test/sampleFiles/expectedEmptyFileResult.html new file mode 100644 index 0000000..540135a --- /dev/null +++ b/test/sampleFiles/expectedEmptyFileResult.html @@ -0,0 +1 @@ +

\ No newline at end of file From 1ec13fe52404ecbe66272ef5e5b9120fbfc9d754 Mon Sep 17 00:00:00 2001 From: Hugo Migneron Date: Mon, 31 Oct 2016 11:23:41 -0400 Subject: [PATCH 3/5] Added tests for multi selection tag wrap. --- test/extension.test.ts | 67 +++++++++++++++++-- test/sampleFiles/emptyFileMultiline.html | 2 + ...expectedEmptyFileMultipleCursorResult.html | 3 + ... expectedEmptyFileSingleCursorResult.html} | 0 ...ultiSelectionMixedLineBlockFileResult.html | 16 +++++ ...tedMultiSelectionTextBlocksFileResult.html | 20 ++++++ test/sampleFiles/textBlocks.html | 14 ++++ 7 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 test/sampleFiles/emptyFileMultiline.html create mode 100644 test/sampleFiles/expectedEmptyFileMultipleCursorResult.html rename test/sampleFiles/{expectedEmptyFileResult.html => expectedEmptyFileSingleCursorResult.html} (100%) create mode 100644 test/sampleFiles/expectedMultiSelectionMixedLineBlockFileResult.html create mode 100644 test/sampleFiles/expectedMultiSelectionTextBlocksFileResult.html create mode 100644 test/sampleFiles/textBlocks.html diff --git a/test/extension.test.ts b/test/extension.test.ts index af2fe79..22c6be9 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,20 +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 parametrizedTest('emptyFile.html', 'expectedEmptyFileResult.html', new Position(0, 0), new Position(0, 0), 'Empty selection tag wrap does not work'); + 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(0, 0), new Position(0, 0)], + [new Position(1, 0), new Position(1, 0)], + [new Position(2, 0), new Position(2, 0)] + ]; + return parametrizedMultiSelectionTest('emptyFileMultiLine.html', 'expectedEmptyFileMultipleCursorResult.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/emptyFileMultiline.html b/test/sampleFiles/emptyFileMultiline.html new file mode 100644 index 0000000..139597f --- /dev/null +++ b/test/sampleFiles/emptyFileMultiline.html @@ -0,0 +1,2 @@ + + diff --git a/test/sampleFiles/expectedEmptyFileMultipleCursorResult.html b/test/sampleFiles/expectedEmptyFileMultipleCursorResult.html new file mode 100644 index 0000000..76aa1fb --- /dev/null +++ b/test/sampleFiles/expectedEmptyFileMultipleCursorResult.html @@ -0,0 +1,3 @@ +

+

+

\ No newline at end of file diff --git a/test/sampleFiles/expectedEmptyFileResult.html b/test/sampleFiles/expectedEmptyFileSingleCursorResult.html similarity index 100% rename from test/sampleFiles/expectedEmptyFileResult.html rename to test/sampleFiles/expectedEmptyFileSingleCursorResult.html 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 +
+
From 3ddc30fc8c72fb19df3963cc6d606459a3dd2f64 Mon Sep 17 00:00:00 2001 From: Hugo Migneron Date: Mon, 31 Oct 2016 11:33:17 -0400 Subject: [PATCH 4/5] Update readme. Up version number --- README.md | 8 +++++--- package.json | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b8f15cd..251b229 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ [![Build Status](https://travis-ci.org/Microsoft/vscode-htmltagwrap.svg?branch=master)](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).** ![Wrap text in your images](images/screenshot.gif) @@ -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" From c3f4403903f9dae9019aa8079a48759ded7aff21 Mon Sep 17 00:00:00 2001 From: Hugo Migneron Date: Mon, 31 Oct 2016 14:05:23 -0400 Subject: [PATCH 5/5] Fix tests : Empty file with multiple line breaks not playing well with Travis tests on linux. --- test/extension.test.ts | 6 +++--- test/sampleFiles/emptyFileMultiline.html | 2 -- test/sampleFiles/emptySelectionMultipleCursors.html | 5 +++++ test/sampleFiles/expectedEmptyFileMultipleCursorResult.html | 3 --- .../expectedEmptySelectionMultipleCursorsResult.html | 5 +++++ 5 files changed, 13 insertions(+), 8 deletions(-) delete mode 100644 test/sampleFiles/emptyFileMultiline.html create mode 100644 test/sampleFiles/emptySelectionMultipleCursors.html delete mode 100644 test/sampleFiles/expectedEmptyFileMultipleCursorResult.html create mode 100644 test/sampleFiles/expectedEmptySelectionMultipleCursorsResult.html diff --git a/test/extension.test.ts b/test/extension.test.ts index 22c6be9..97819db 100644 --- a/test/extension.test.ts +++ b/test/extension.test.ts @@ -77,11 +77,11 @@ suite('Extension Tests', function () { // Multiple selecetion tests test('Multiple Empty selections line wrap test', function() { const selections: Array = [ - [new Position(0, 0), new Position(0, 0)], [new Position(1, 0), new Position(1, 0)], - [new Position(2, 0), new Position(2, 0)] + [new Position(2, 0), new Position(2, 0)], + [new Position(3, 0), new Position(3, 0)] ]; - return parametrizedMultiSelectionTest('emptyFileMultiLine.html', 'expectedEmptyFileMultipleCursorResult.html', selections, 'Empty selection tag wrap does not work with multiple selections'); + return parametrizedMultiSelectionTest('emptySelectionMultipleCursors.html', 'expectedEmptySelectionMultipleCursorsResult.html', selections, 'Empty selection tag wrap does not work with multiple selections'); }); test('Multiple selections block wrap test', function() { diff --git a/test/sampleFiles/emptyFileMultiline.html b/test/sampleFiles/emptyFileMultiline.html deleted file mode 100644 index 139597f..0000000 --- a/test/sampleFiles/emptyFileMultiline.html +++ /dev/null @@ -1,2 +0,0 @@ - - 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/expectedEmptyFileMultipleCursorResult.html b/test/sampleFiles/expectedEmptyFileMultipleCursorResult.html deleted file mode 100644 index 76aa1fb..0000000 --- a/test/sampleFiles/expectedEmptyFileMultipleCursorResult.html +++ /dev/null @@ -1,3 +0,0 @@ -

-

-

\ 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