Skip to content
This repository was archived by the owner on Oct 12, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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?
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
127 changes: 68 additions & 59 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,88 +18,97 @@ 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
// The commandId parameter must match the command field in package.json
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 + '</p>');
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--) {
console.log('FOR Loop line #: ' + lineNumber);
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), '<p>\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), '<p>');
editBuilder.insert(new vscode.Position(selectionEnd.line, selectionEnd.character), '</p>');
}
}
else {
//Wrap it inline
editor.edit((editBuilder) => {
editBuilder.insert(new vscode.Position(selectionEnd.line, selectionEnd.character), '</p>');
editBuilder.insert(new vscode.Position(selectionEnd.line, selectionStart.character), '<p>');
}).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<vscode.Selection> = new Array<vscode.Selection>();

for(let selection of selections) {

// Careful : the selection starts at the beginning of the text but ends *after* the closing </p> 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 </p> 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);
});
});
}
68 changes: 62 additions & 6 deletions test/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<CursorSelection> = [selection];

return parametrizedMultiSelectionTest(startFilePath, expectedResultFilePath, selections, failMessage);
}

function parametrizedMultiSelectionTest(startFilePath: string, expectedResultFilePath: string, selections: Array<CursorSelection>, failMessage: string) {
let result: string;
let expectedResult: string;
let editor: TextEditor;
Expand All @@ -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();
Expand All @@ -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<CursorSelection> = [
[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<CursorSelection> = [
[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<CursorSelection> = [
[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<CursorSelection> = [
[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));
});
Empty file added test/sampleFiles/emptyFile.html
Empty file.
5 changes: 5 additions & 0 deletions test/sampleFiles/emptySelectionMultipleCursors.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div>content</div>



<div>content</div>
1 change: 1 addition & 0 deletions test/sampleFiles/expectedEmptyFileSingleCursorResult.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p></p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div>content</div>
<p></p>
<p></p>
<p></p>
<div>content</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<div>
<p>this is the first</p>
<p>block of text</p>
</div>

<p>
this is the second
block of text
</p>

<div>
<div>
<p>this is the</p> third block
of <p>text</p>
</div>
</div>
20 changes: 20 additions & 0 deletions test/sampleFiles/expectedMultiSelectionTextBlocksFileResult.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<div>
<p>
this is the first
block of text
</p>
</div>

<p>
this is the second
block of text
</p>

<div>
<div>
<p>
this is the third block
of text
</p>
</div>
</div>
14 changes: 14 additions & 0 deletions test/sampleFiles/textBlocks.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<div>
this is the first
block of text
</div>

this is the second
block of text

<div>
<div>
this is the third block
of text
</div>
</div>