Emmet: when wrapping, skip lines with no characters selected (#45224)

* When wrapping using Emmet, don't consider lines with no characters selected.

* Adding unit tests

* Adding support for multicursors in wrapIndividualLinesWithAbbreviation

* Moving check in multicursor wrapIndividualLines

* Make sure when expanding several abbreviations with different snippets that the edits are applied in reverse order.
This commit is contained in:
Gus Hurovich 2018-03-09 16:49:07 -08:00 committed by Ramya Rao
parent ec1b9fe38b
commit 16b91c78f4
2 changed files with 77 additions and 20 deletions

View file

@ -51,7 +51,11 @@ export function wrapWithAbbreviation(args: any) {
editor.selections.sort((a: vscode.Selection, b: vscode.Selection) => { return a.start.line - b.start.line; }).forEach(selection => {
let rangeToReplace: vscode.Range = selection.isReversed ? new vscode.Range(selection.active, selection.anchor) : selection;
if (rangeToReplace.isEmpty) {
if (!rangeToReplace.isSingleLine && rangeToReplace.end.character === 0) {
let previousLine = rangeToReplace.end.line - 1;
let lastChar = editor.document.lineAt(previousLine).text.length;
rangeToReplace = new vscode.Range(rangeToReplace.start, new vscode.Position(previousLine, lastChar));
} else if (rangeToReplace.isEmpty) {
let { active } = selection;
let currentNode = getNode(rootNode, active, true);
if (currentNode && (currentNode.start.line === active.line || currentNode.end.line === active.line)) {
@ -61,11 +65,7 @@ export function wrapWithAbbreviation(args: any) {
}
}
const firstLineOfSelection = editor.document.lineAt(rangeToReplace.start).text.substr(rangeToReplace.start.character);
const matches = firstLineOfSelection.match(/^(\s*)/);
const extraWhiteSpaceSelected = matches ? matches[1].length : 0;
rangeToReplace = new vscode.Range(rangeToReplace.start.line, rangeToReplace.start.character + extraWhiteSpaceSelected, rangeToReplace.end.line, rangeToReplace.end.character);
rangeToReplace = ignoreExtraWhitespaceSelected(rangeToReplace, editor.document);
const wholeFirstLine = editor.document.lineAt(rangeToReplace.start).text;
const otherMatches = wholeFirstLine.match(/^(\s*)/);
@ -184,10 +184,26 @@ export function wrapIndividualLinesWithAbbreviation(args: any) {
}
const editor = vscode.window.activeTextEditor;
if (editor.selection.isEmpty) {
if (editor.selections.length === 1 && editor.selection.isEmpty) {
vscode.window.showInformationMessage('Select more than 1 line and try again.');
return;
}
if (editor.selections.find(x => x.isEmpty)) {
vscode.window.showInformationMessage('Select more than 1 line in each selection and try again.');
return;
}
let rangesToReplace: vscode.Range[] = [];
editor.selections.forEach(selection => {
let rangeToReplace: vscode.Range = selection.isReversed ? new vscode.Range(selection.active, selection.anchor) : selection;
if (!rangeToReplace.isSingleLine && rangeToReplace.end.character === 0) {
let previousLine = rangeToReplace.end.line - 1;
let lastChar = editor.document.lineAt(previousLine).text.length;
rangeToReplace = new vscode.Range(rangeToReplace.start, new vscode.Position(previousLine, lastChar));
}
rangeToReplace = ignoreExtraWhitespaceSelected(rangeToReplace, editor.document);
rangesToReplace.push(rangeToReplace);
});
const syntax = getSyntaxFromArgs({ language: editor.document.languageId });
if (!syntax) {
@ -195,31 +211,43 @@ export function wrapIndividualLinesWithAbbreviation(args: any) {
}
const abbreviationPromise = (args && args['abbreviation']) ? Promise.resolve(args['abbreviation']) : vscode.window.showInputBox({ prompt: 'Enter Abbreviation' });
const lines = editor.document.getText(editor.selection).split('\n').map(x => x.trim());
const helper = getEmmetHelper();
return abbreviationPromise.then(inputAbbreviation => {
let expandAbbrInput: ExpandAbbreviationInput[] = [];
if (!inputAbbreviation || !inputAbbreviation.trim() || !helper.isAbbreviationValid(syntax, inputAbbreviation)) { return false; }
let extractedResults = helper.extractAbbreviationFromText(inputAbbreviation);
if (!extractedResults) {
return false;
}
rangesToReplace.forEach(rangeToReplace => {
let lines = editor.document.getText(rangeToReplace).split('\n').map(x => x.trim());
let { abbreviation, filter } = extractedResults;
let input: ExpandAbbreviationInput = {
syntax,
abbreviation,
rangeToReplace: editor.selection,
textToWrap: lines,
filter
};
let { abbreviation, filter } = extractedResults;
let input: ExpandAbbreviationInput = {
syntax,
abbreviation,
rangeToReplace,
textToWrap: lines,
filter
};
expandAbbrInput.push(input);
});
return expandAbbreviationInRange(editor, [input], true);
return expandAbbreviationInRange(editor, expandAbbrInput, false);
});
}
function ignoreExtraWhitespaceSelected(range: vscode.Range, document: vscode.TextDocument): vscode.Range {
const firstLineOfSelection = document.lineAt(range.start).text.substr(range.start.character);
const matches = firstLineOfSelection.match(/^(\s*)/);
const extraWhiteSpaceSelected = matches ? matches[1].length : 0;
return new vscode.Range(range.start.line, range.start.character + extraWhiteSpaceSelected, range.end.line, range.end.character);
}
export function expandEmmetAbbreviation(args: any): Thenable<boolean | undefined> {
if (!validate() || !vscode.window.activeTextEditor) {
return fallbackTab();
@ -493,10 +521,10 @@ function expandAbbreviationInRange(editor: vscode.TextEditor, expandAbbrList: Ex
// We will not be able to maintain multiple cursors after snippet insertion
let insertPromises: Thenable<boolean>[] = [];
if (!insertSameSnippet) {
expandAbbrList.forEach((expandAbbrInput: ExpandAbbreviationInput) => {
expandAbbrList.sort((a: ExpandAbbreviationInput, b: ExpandAbbreviationInput) => { return b.rangeToReplace.start.compareTo(a.rangeToReplace.start); }).forEach((expandAbbrInput: ExpandAbbreviationInput) => {
let expandedText = expandAbbr(expandAbbrInput);
if (expandedText) {
insertPromises.push(editor.insertSnippet(new vscode.SnippetString(expandedText), expandAbbrInput.rangeToReplace));
insertPromises.push(editor.insertSnippet(new vscode.SnippetString(expandedText), expandAbbrInput.rangeToReplace, { undoStopBefore: false, undoStopAfter: false }));
}
});
if (insertPromises.length === 0) {

View file

@ -61,7 +61,7 @@ suite('Tests for Wrap with Abbreviations', () => {
const multiCursors = [new Selection(2, 6, 2, 6), new Selection(3, 6, 3, 6)];
const multiCursorsWithSelection = [new Selection(2, 2, 2, 28), new Selection(3, 2, 3, 33)];
const multiCursorsWithFullLineSelection = [new Selection(2, 0, 2, 28), new Selection(3, 0, 3, 33)];
const multiCursorsWithFullLineSelection = [new Selection(2, 0, 2, 28), new Selection(3, 0, 4, 0)];
test('Wrap with block element using multi cursor', () => {
@ -251,6 +251,35 @@ suite('Tests for Wrap with Abbreviations', () => {
});
});
test('Wrap individual lines with abbreviation with extra space selected', () => {
const contents = `
<ul class="nav main">
<li class="item1">img</li>
<li class="item2">hi.there</li>
</ul>
`;
const wrapIndividualLinesExpected = `
<ul class="nav main">
<ul>
<li class="hello1"><li class="item1">img</li></li>
<li class="hello2"><li class="item2">hi.there</li></li>
</ul>
</ul>
`;
return withRandomFileEditor(contents, 'html', (editor, doc) => {
editor.selections = [new Selection(2, 1, 4, 0)];
const promise = wrapIndividualLinesWithAbbreviation({ abbreviation: 'ul>li.hello$*' });
if (!promise) {
assert.equal(1, 2, 'Wrap Individual Lines with Abbreviation returned undefined.');
return Promise.resolve();
}
return promise.then(() => {
assert.equal(editor.document.getText(), wrapIndividualLinesExpected);
return Promise.resolve();
});
});
});
test('Wrap individual lines with abbreviation with comment filter', () => {
const contents = `
<ul class="nav main">