diff --git a/extensions/emmet/src/abbreviationActions.ts b/extensions/emmet/src/abbreviationActions.ts index da56664ca2e06..d238d90e798a7 100644 --- a/extensions/emmet/src/abbreviationActions.ts +++ b/extensions/emmet/src/abbreviationActions.ts @@ -32,32 +32,49 @@ interface PreviewRangesWithContent { } export function wrapWithAbbreviation(args: any) { + return doWrapping(false, args); +} + +export function wrapIndividualLinesWithAbbreviation(args: any) { + return doWrapping(true, args); +} + +function doWrapping(individualLines: boolean, args: any) { if (!validate(false) || !vscode.window.activeTextEditor) { return; } const editor = vscode.window.activeTextEditor; - let rootNode = parseDocument(editor.document, false); - - const syntax = getSyntaxFromArgs({ language: editor.document.languageId }) || ''; + const rootNode = parseDocument(editor.document, false); + if (individualLines) { + 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; + } + } + const syntax = getSyntaxFromArgs({ language: editor.document.languageId }); if (!syntax) { return; } let inPreview = false; + let currentValue = ''; + const helper = getEmmetHelper(); // Fetch general information for the succesive expansions. i.e. the ranges to replace and its contents - let rangesToReplace: PreviewRangesWithContent[] = []; - - editor.selections.sort((a: vscode.Selection, b: vscode.Selection) => { return a.start.line - b.start.line; }).forEach(selection => { + let rangesToReplace: PreviewRangesWithContent[] = editor.selections.sort((a: vscode.Selection, b: vscode.Selection) => { return a.start.compareTo(b.start); }).map(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; + const previousLine = rangeToReplace.end.line - 1; + const 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); + const { active } = selection; + const currentNode = getNode(rootNode, active, true); if (currentNode && (currentNode.start.line === active.line || currentNode.end.line === active.line)) { rangeToReplace = new vscode.Range(currentNode.start, currentNode.end); } else { @@ -65,37 +82,80 @@ export function wrapWithAbbreviation(args: any) { } } - rangeToReplace = ignoreExtraWhitespaceSelected(rangeToReplace, editor.document); + 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); - const wholeFirstLine = editor.document.lineAt(rangeToReplace.start).text; - const otherMatches = wholeFirstLine.match(/^(\s*)/); - const preceedingWhiteSpace = otherMatches ? otherMatches[1] : ''; + let textToWrapInPreview: string[]; let textToReplace = editor.document.getText(rangeToReplace); - let textToWrapInPreview = rangeToReplace.isSingleLine ? [textToReplace] : ['\n\t' + textToReplace.split('\n' + preceedingWhiteSpace).join('\n\t') + '\n']; - rangesToReplace.push({ previewRange: rangeToReplace, originalRange: rangeToReplace, originalContent: textToReplace, textToWrapInPreview }); + if (individualLines) { + textToWrapInPreview = textToReplace.split('\n').map(x => x.trim()); + } else { + const wholeFirstLine = editor.document.lineAt(rangeToReplace.start).text; + const otherMatches = wholeFirstLine.match(/^(\s*)/); + const preceedingWhiteSpace = otherMatches ? otherMatches[1] : ''; + textToWrapInPreview = rangeToReplace.isSingleLine ? [textToReplace] : ['\n\t' + textToReplace.split('\n' + preceedingWhiteSpace).join('\n\t') + '\n']; + } + + return { + previewRange: rangeToReplace, + originalRange: rangeToReplace, + originalContent: textToReplace, + textToWrapInPreview + }; }); - let abbreviationPromise; - let currentValue = ''; + function revertPreview(): Thenable { + return editor.edit(builder => { + for (let i = 0; i < rangesToReplace.length; i++) { + builder.replace(rangesToReplace[i].previewRange, rangesToReplace[i].originalContent); + rangesToReplace[i].previewRange = rangesToReplace[i].originalRange; + } + }, { undoStopBefore: false, undoStopAfter: false }); + } - function inputChanged(value: string): string { - if (value !== currentValue) { - currentValue = value; - makeChanges(value, inPreview, false).then((out) => { - if (typeof out === 'boolean') { - inPreview = out; + function applyPreview(expandAbbrList: ExpandAbbreviationInput[]): Thenable { + let totalLinesInserted = 0; + + return editor.edit(builder => { + for (let i = 0; i < rangesToReplace.length; i++) { + const expandedText = expandAbbr(expandAbbrList[i]) || ''; + if (!expandedText) { + // Failed to expand text. We already showed an error inside expandAbbr. + break; } - }); - } - return ''; - } - abbreviationPromise = (args && args['abbreviation']) ? Promise.resolve(args['abbreviation']) : vscode.window.showInputBox({ prompt: 'Enter Abbreviation', validateInput: inputChanged }); - const helper = getEmmetHelper(); + const oldPreviewRange = rangesToReplace[i].previewRange; + const preceedingText = editor.document.getText(new vscode.Range(oldPreviewRange.start.line, 0, oldPreviewRange.start.line, oldPreviewRange.start.character)); + const indentPrefix = (preceedingText.match(/^(\s*)/) || ['', ''])[1]; + + let newText = expandedText.replace(/\n/g, '\n' + indentPrefix); // Adding indentation on each line of expanded text + newText = newText.replace(/\$\{[\d]*\}/g, '|'); // Removing Tabstops + newText = newText.replace(/\$\{[\d]*(:[^}]*)?\}/g, (match) => { // Replacing Placeholders + return match.replace(/^\$\{[\d]*:/, '').replace('}', ''); + }); + builder.replace(oldPreviewRange, newText); + + const expandedTextLines = newText.split('\n'); + const oldPreviewLines = oldPreviewRange.end.line - oldPreviewRange.start.line + 1; + const newLinesInserted = expandedTextLines.length - oldPreviewLines; + + let lastLineEnd = expandedTextLines[expandedTextLines.length - 1].length; + if (expandedTextLines.length === 1) { + // If the expandedText is single line, add the length of preceeding whitespace as it will not be included in line length. + lastLineEnd += oldPreviewRange.start.character; + } + + rangesToReplace[i].previewRange = new vscode.Range(oldPreviewRange.start.line + totalLinesInserted, oldPreviewRange.start.character, oldPreviewRange.end.line + totalLinesInserted + newLinesInserted, lastLineEnd); + totalLinesInserted += newLinesInserted; + } + }, { undoStopBefore: false, undoStopAfter: false }); + } - function makeChanges(inputAbbreviation: string | undefined, inPreview: boolean, definitive: boolean): Thenable { + function makeChanges(inputAbbreviation: string | undefined, definitive: boolean): Thenable { if (!inputAbbreviation || !inputAbbreviation.trim() || !helper.isAbbreviationValid(syntax, inputAbbreviation)) { - return inPreview ? revertPreview(editor, rangesToReplace).then(() => { return false; }) : Promise.resolve(inPreview); + return inPreview ? revertPreview().then(() => { return false; }) : Promise.resolve(inPreview); } let extractedResults = helper.extractAbbreviationFromText(inputAbbreviation); @@ -107,145 +167,44 @@ export function wrapWithAbbreviation(args: any) { let { abbreviation, filter } = extractedResults; if (definitive) { - const revertPromise = inPreview ? revertPreview(editor, rangesToReplace) : Promise.resolve(); + const revertPromise = inPreview ? revertPreview() : Promise.resolve(); return revertPromise.then(() => { const expandAbbrList: ExpandAbbreviationInput[] = rangesToReplace.map(rangesAndContent => { let rangeToReplace = rangesAndContent.originalRange; - let textToWrap = rangeToReplace.isSingleLine ? ['$TM_SELECTED_TEXT'] : ['\n\t$TM_SELECTED_TEXT\n']; - return { syntax, abbreviation, rangeToReplace, textToWrap, filter }; + let textToWrap: string[]; + if (individualLines) { + textToWrap = rangesAndContent.textToWrapInPreview; + } else { + textToWrap = rangeToReplace.isSingleLine ? ['$TM_SELECTED_TEXT'] : ['\n\t$TM_SELECTED_TEXT\n']; + } + return { syntax: syntax || '', abbreviation, rangeToReplace, textToWrap, filter }; }); - return expandAbbreviationInRange(editor, expandAbbrList, true).then(() => { return true; }); + return expandAbbreviationInRange(editor, expandAbbrList, !individualLines).then(() => { return true; }); }); } const expandAbbrList: ExpandAbbreviationInput[] = rangesToReplace.map(rangesAndContent => { - return { syntax, abbreviation, rangeToReplace: rangesAndContent.originalRange, textToWrap: rangesAndContent.textToWrapInPreview, filter }; + return { syntax: syntax || '', abbreviation, rangeToReplace: rangesAndContent.originalRange, textToWrap: rangesAndContent.textToWrapInPreview, filter }; }); - return applyPreview(editor, expandAbbrList, rangesToReplace); + return applyPreview(expandAbbrList); } - // On inputBox closing - return abbreviationPromise.then(inputAbbreviation => { - return makeChanges(inputAbbreviation, inPreview, true); - }); -} - -function revertPreview(editor: vscode.TextEditor, rangesToReplace: PreviewRangesWithContent[]): Thenable { - return editor.edit(builder => { - for (let i = 0; i < rangesToReplace.length; i++) { - builder.replace(rangesToReplace[i].previewRange, rangesToReplace[i].originalContent); - rangesToReplace[i].previewRange = rangesToReplace[i].originalRange; - } - }, { undoStopBefore: false, undoStopAfter: false }); -} - -function applyPreview(editor: vscode.TextEditor, expandAbbrList: ExpandAbbreviationInput[], rangesToReplace: PreviewRangesWithContent[]): Thenable { - let totalLinesInserted = 0; - - return editor.edit(builder => { - for (let i = 0; i < rangesToReplace.length; i++) { - const expandedText = expandAbbr(expandAbbrList[i]) || ''; - if (!expandedText) { - // Failed to expand text. We already showed an error inside expandAbbr. - break; - } - - const oldPreviewRange = rangesToReplace[i].previewRange; - const preceedingText = editor.document.getText(new vscode.Range(oldPreviewRange.start.line, 0, oldPreviewRange.start.line, oldPreviewRange.start.character)); - const indentPrefix = (preceedingText.match(/^(\s*)/) || ['', ''])[1]; - - let newText = expandedText.replace(/\n/g, '\n' + indentPrefix); // Adding indentation on each line of expanded text - newText = newText.replace(/\$\{[\d]*\}/g, '|'); // Removing Tabstops - newText = newText.replace(/\$\{[\d]*(:[^}]*)?\}/g, (match) => { // Replacing Placeholders - return match.replace(/^\$\{[\d]*:/, '').replace('}', ''); + function inputChanged(value: string): string { + if (value !== currentValue) { + currentValue = value; + makeChanges(value, false).then((out) => { + if (typeof out === 'boolean') { + inPreview = out; + } }); - builder.replace(oldPreviewRange, newText); - - const expandedTextLines = newText.split('\n'); - const oldPreviewLines = oldPreviewRange.end.line - oldPreviewRange.start.line + 1; - const newLinesInserted = expandedTextLines.length - oldPreviewLines; - - let lastLineEnd = expandedTextLines[expandedTextLines.length - 1].length; - if (expandedTextLines.length === 1) { - // If the expandedText is single line, add the length of preceeding whitespace as it will not be included in line length. - lastLineEnd += oldPreviewRange.start.character; - } - - rangesToReplace[i].previewRange = new vscode.Range(oldPreviewRange.start.line + totalLinesInserted, oldPreviewRange.start.character, oldPreviewRange.end.line + totalLinesInserted + newLinesInserted, lastLineEnd); - totalLinesInserted += newLinesInserted; } - }, { undoStopBefore: false, undoStopAfter: false }); -} - -export function wrapIndividualLinesWithAbbreviation(args: any) { - if (!validate(false) || !vscode.window.activeTextEditor) { - return; - } - - const editor = vscode.window.activeTextEditor; - 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) { - return; + return ''; } - - const abbreviationPromise = (args && args['abbreviation']) ? Promise.resolve(args['abbreviation']) : vscode.window.showInputBox({ prompt: 'Enter Abbreviation' }); - const helper = getEmmetHelper(); - + const abbreviationPromise = (args && args['abbreviation']) ? Promise.resolve(args['abbreviation']) : vscode.window.showInputBox({ prompt: 'Enter Abbreviation', validateInput: inputChanged }); 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, - textToWrap: lines, - filter - }; - expandAbbrInput.push(input); - }); - - return expandAbbreviationInRange(editor, expandAbbrInput, false); + return makeChanges(inputAbbreviation, true); }); - -} - -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 {