Adding Live Preview for Wrapping Individual Lines with Abbreviation (#45453)
* Adding Live Preview to wrapIndividualLinesWithAbbreviation * Refactoring * Re-arranging code
This commit is contained in:
parent
4a5e48de17
commit
a716373aaa
1 changed files with 114 additions and 155 deletions
|
@ -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 inputChanged(value: string): string {
|
||||
if (value !== currentValue) {
|
||||
currentValue = value;
|
||||
makeChanges(value, inPreview, false).then((out) => {
|
||||
if (typeof out === 'boolean') {
|
||||
inPreview = out;
|
||||
}
|
||||
});
|
||||
}
|
||||
return '';
|
||||
function revertPreview(): Thenable<any> {
|
||||
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 });
|
||||
}
|
||||
|
||||
abbreviationPromise = (args && args['abbreviation']) ? Promise.resolve(args['abbreviation']) : vscode.window.showInputBox({ prompt: 'Enter Abbreviation', validateInput: inputChanged });
|
||||
const helper = getEmmetHelper();
|
||||
function applyPreview(expandAbbrList: ExpandAbbreviationInput[]): Thenable<boolean> {
|
||||
let totalLinesInserted = 0;
|
||||
|
||||
function makeChanges(inputAbbreviation: string | undefined, inPreview: boolean, definitive: boolean): Thenable<boolean> {
|
||||
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('}', '');
|
||||
});
|
||||
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, definitive: boolean): Thenable<boolean> {
|
||||
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<any> {
|
||||
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<boolean> {
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
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<boolean | undefined> {
|
||||
|
|
Loading…
Reference in a new issue