Skip to content

Commit a716373

Browse files
gushuroramya-rao-a
authored andcommitted
Adding Live Preview for Wrapping Individual Lines with Abbreviation (microsoft#45453)
* Adding Live Preview to wrapIndividualLinesWithAbbreviation * Refactoring * Re-arranging code
1 parent 4a5e48d commit a716373

1 file changed

Lines changed: 113 additions & 154 deletions

File tree

extensions/emmet/src/abbreviationActions.ts

Lines changed: 113 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -32,70 +32,130 @@ interface PreviewRangesWithContent {
3232
}
3333

3434
export function wrapWithAbbreviation(args: any) {
35+
return doWrapping(false, args);
36+
}
37+
38+
export function wrapIndividualLinesWithAbbreviation(args: any) {
39+
return doWrapping(true, args);
40+
}
41+
42+
function doWrapping(individualLines: boolean, args: any) {
3543
if (!validate(false) || !vscode.window.activeTextEditor) {
3644
return;
3745
}
3846

3947
const editor = vscode.window.activeTextEditor;
40-
let rootNode = parseDocument(editor.document, false);
41-
42-
const syntax = getSyntaxFromArgs({ language: editor.document.languageId }) || '';
48+
const rootNode = parseDocument(editor.document, false);
49+
if (individualLines) {
50+
if (editor.selections.length === 1 && editor.selection.isEmpty) {
51+
vscode.window.showInformationMessage('Select more than 1 line and try again.');
52+
return;
53+
}
54+
if (editor.selections.find(x => x.isEmpty)) {
55+
vscode.window.showInformationMessage('Select more than 1 line in each selection and try again.');
56+
return;
57+
}
58+
}
59+
const syntax = getSyntaxFromArgs({ language: editor.document.languageId });
4360
if (!syntax) {
4461
return;
4562
}
4663

4764
let inPreview = false;
65+
let currentValue = '';
66+
const helper = getEmmetHelper();
4867

4968
// Fetch general information for the succesive expansions. i.e. the ranges to replace and its contents
50-
let rangesToReplace: PreviewRangesWithContent[] = [];
51-
52-
editor.selections.sort((a: vscode.Selection, b: vscode.Selection) => { return a.start.line - b.start.line; }).forEach(selection => {
69+
let rangesToReplace: PreviewRangesWithContent[] = editor.selections.sort((a: vscode.Selection, b: vscode.Selection) => { return a.start.compareTo(b.start); }).map(selection => {
5370
let rangeToReplace: vscode.Range = selection.isReversed ? new vscode.Range(selection.active, selection.anchor) : selection;
5471
if (!rangeToReplace.isSingleLine && rangeToReplace.end.character === 0) {
55-
let previousLine = rangeToReplace.end.line - 1;
56-
let lastChar = editor.document.lineAt(previousLine).text.length;
72+
const previousLine = rangeToReplace.end.line - 1;
73+
const lastChar = editor.document.lineAt(previousLine).text.length;
5774
rangeToReplace = new vscode.Range(rangeToReplace.start, new vscode.Position(previousLine, lastChar));
5875
} else if (rangeToReplace.isEmpty) {
59-
let { active } = selection;
60-
let currentNode = getNode(rootNode, active, true);
76+
const { active } = selection;
77+
const currentNode = getNode(rootNode, active, true);
6178
if (currentNode && (currentNode.start.line === active.line || currentNode.end.line === active.line)) {
6279
rangeToReplace = new vscode.Range(currentNode.start, currentNode.end);
6380
} else {
6481
rangeToReplace = new vscode.Range(rangeToReplace.start.line, 0, rangeToReplace.start.line, editor.document.lineAt(rangeToReplace.start.line).text.length);
6582
}
6683
}
6784

68-
rangeToReplace = ignoreExtraWhitespaceSelected(rangeToReplace, editor.document);
85+
const firstLineOfSelection = editor.document.lineAt(rangeToReplace.start).text.substr(rangeToReplace.start.character);
86+
const matches = firstLineOfSelection.match(/^(\s*)/);
87+
const extraWhiteSpaceSelected = matches ? matches[1].length : 0;
88+
rangeToReplace = new vscode.Range(rangeToReplace.start.line, rangeToReplace.start.character + extraWhiteSpaceSelected, rangeToReplace.end.line, rangeToReplace.end.character);
6989

70-
const wholeFirstLine = editor.document.lineAt(rangeToReplace.start).text;
71-
const otherMatches = wholeFirstLine.match(/^(\s*)/);
72-
const preceedingWhiteSpace = otherMatches ? otherMatches[1] : '';
90+
let textToWrapInPreview: string[];
7391
let textToReplace = editor.document.getText(rangeToReplace);
74-
let textToWrapInPreview = rangeToReplace.isSingleLine ? [textToReplace] : ['\n\t' + textToReplace.split('\n' + preceedingWhiteSpace).join('\n\t') + '\n'];
75-
rangesToReplace.push({ previewRange: rangeToReplace, originalRange: rangeToReplace, originalContent: textToReplace, textToWrapInPreview });
92+
if (individualLines) {
93+
textToWrapInPreview = textToReplace.split('\n').map(x => x.trim());
94+
} else {
95+
const wholeFirstLine = editor.document.lineAt(rangeToReplace.start).text;
96+
const otherMatches = wholeFirstLine.match(/^(\s*)/);
97+
const preceedingWhiteSpace = otherMatches ? otherMatches[1] : '';
98+
textToWrapInPreview = rangeToReplace.isSingleLine ? [textToReplace] : ['\n\t' + textToReplace.split('\n' + preceedingWhiteSpace).join('\n\t') + '\n'];
99+
}
100+
101+
return {
102+
previewRange: rangeToReplace,
103+
originalRange: rangeToReplace,
104+
originalContent: textToReplace,
105+
textToWrapInPreview
106+
};
76107
});
77108

78-
let abbreviationPromise;
79-
let currentValue = '';
109+
function revertPreview(): Thenable<any> {
110+
return editor.edit(builder => {
111+
for (let i = 0; i < rangesToReplace.length; i++) {
112+
builder.replace(rangesToReplace[i].previewRange, rangesToReplace[i].originalContent);
113+
rangesToReplace[i].previewRange = rangesToReplace[i].originalRange;
114+
}
115+
}, { undoStopBefore: false, undoStopAfter: false });
116+
}
80117

81-
function inputChanged(value: string): string {
82-
if (value !== currentValue) {
83-
currentValue = value;
84-
makeChanges(value, inPreview, false).then((out) => {
85-
if (typeof out === 'boolean') {
86-
inPreview = out;
118+
function applyPreview(expandAbbrList: ExpandAbbreviationInput[]): Thenable<boolean> {
119+
let totalLinesInserted = 0;
120+
121+
return editor.edit(builder => {
122+
for (let i = 0; i < rangesToReplace.length; i++) {
123+
const expandedText = expandAbbr(expandAbbrList[i]) || '';
124+
if (!expandedText) {
125+
// Failed to expand text. We already showed an error inside expandAbbr.
126+
break;
87127
}
88-
});
89-
}
90-
return '';
91-
}
92128

93-
abbreviationPromise = (args && args['abbreviation']) ? Promise.resolve(args['abbreviation']) : vscode.window.showInputBox({ prompt: 'Enter Abbreviation', validateInput: inputChanged });
94-
const helper = getEmmetHelper();
129+
const oldPreviewRange = rangesToReplace[i].previewRange;
130+
const preceedingText = editor.document.getText(new vscode.Range(oldPreviewRange.start.line, 0, oldPreviewRange.start.line, oldPreviewRange.start.character));
131+
const indentPrefix = (preceedingText.match(/^(\s*)/) || ['', ''])[1];
132+
133+
let newText = expandedText.replace(/\n/g, '\n' + indentPrefix); // Adding indentation on each line of expanded text
134+
newText = newText.replace(/\$\{[\d]*\}/g, '|'); // Removing Tabstops
135+
newText = newText.replace(/\$\{[\d]*(:[^}]*)?\}/g, (match) => { // Replacing Placeholders
136+
return match.replace(/^\$\{[\d]*:/, '').replace('}', '');
137+
});
138+
builder.replace(oldPreviewRange, newText);
139+
140+
const expandedTextLines = newText.split('\n');
141+
const oldPreviewLines = oldPreviewRange.end.line - oldPreviewRange.start.line + 1;
142+
const newLinesInserted = expandedTextLines.length - oldPreviewLines;
143+
144+
let lastLineEnd = expandedTextLines[expandedTextLines.length - 1].length;
145+
if (expandedTextLines.length === 1) {
146+
// If the expandedText is single line, add the length of preceeding whitespace as it will not be included in line length.
147+
lastLineEnd += oldPreviewRange.start.character;
148+
}
149+
150+
rangesToReplace[i].previewRange = new vscode.Range(oldPreviewRange.start.line + totalLinesInserted, oldPreviewRange.start.character, oldPreviewRange.end.line + totalLinesInserted + newLinesInserted, lastLineEnd);
151+
totalLinesInserted += newLinesInserted;
152+
}
153+
}, { undoStopBefore: false, undoStopAfter: false });
154+
}
95155

96-
function makeChanges(inputAbbreviation: string | undefined, inPreview: boolean, definitive: boolean): Thenable<boolean> {
156+
function makeChanges(inputAbbreviation: string | undefined, definitive: boolean): Thenable<boolean> {
97157
if (!inputAbbreviation || !inputAbbreviation.trim() || !helper.isAbbreviationValid(syntax, inputAbbreviation)) {
98-
return inPreview ? revertPreview(editor, rangesToReplace).then(() => { return false; }) : Promise.resolve(inPreview);
158+
return inPreview ? revertPreview().then(() => { return false; }) : Promise.resolve(inPreview);
99159
}
100160

101161
let extractedResults = helper.extractAbbreviationFromText(inputAbbreviation);
@@ -107,145 +167,44 @@ export function wrapWithAbbreviation(args: any) {
107167

108168
let { abbreviation, filter } = extractedResults;
109169
if (definitive) {
110-
const revertPromise = inPreview ? revertPreview(editor, rangesToReplace) : Promise.resolve();
170+
const revertPromise = inPreview ? revertPreview() : Promise.resolve();
111171
return revertPromise.then(() => {
112172
const expandAbbrList: ExpandAbbreviationInput[] = rangesToReplace.map(rangesAndContent => {
113173
let rangeToReplace = rangesAndContent.originalRange;
114-
let textToWrap = rangeToReplace.isSingleLine ? ['$TM_SELECTED_TEXT'] : ['\n\t$TM_SELECTED_TEXT\n'];
115-
return { syntax, abbreviation, rangeToReplace, textToWrap, filter };
174+
let textToWrap: string[];
175+
if (individualLines) {
176+
textToWrap = rangesAndContent.textToWrapInPreview;
177+
} else {
178+
textToWrap = rangeToReplace.isSingleLine ? ['$TM_SELECTED_TEXT'] : ['\n\t$TM_SELECTED_TEXT\n'];
179+
}
180+
return { syntax: syntax || '', abbreviation, rangeToReplace, textToWrap, filter };
116181
});
117-
return expandAbbreviationInRange(editor, expandAbbrList, true).then(() => { return true; });
182+
return expandAbbreviationInRange(editor, expandAbbrList, !individualLines).then(() => { return true; });
118183
});
119184
}
120185

121186
const expandAbbrList: ExpandAbbreviationInput[] = rangesToReplace.map(rangesAndContent => {
122-
return { syntax, abbreviation, rangeToReplace: rangesAndContent.originalRange, textToWrap: rangesAndContent.textToWrapInPreview, filter };
187+
return { syntax: syntax || '', abbreviation, rangeToReplace: rangesAndContent.originalRange, textToWrap: rangesAndContent.textToWrapInPreview, filter };
123188
});
124189

125-
return applyPreview(editor, expandAbbrList, rangesToReplace);
190+
return applyPreview(expandAbbrList);
126191
}
127192

128-
// On inputBox closing
129-
return abbreviationPromise.then(inputAbbreviation => {
130-
return makeChanges(inputAbbreviation, inPreview, true);
131-
});
132-
}
133-
134-
function revertPreview(editor: vscode.TextEditor, rangesToReplace: PreviewRangesWithContent[]): Thenable<any> {
135-
return editor.edit(builder => {
136-
for (let i = 0; i < rangesToReplace.length; i++) {
137-
builder.replace(rangesToReplace[i].previewRange, rangesToReplace[i].originalContent);
138-
rangesToReplace[i].previewRange = rangesToReplace[i].originalRange;
139-
}
140-
}, { undoStopBefore: false, undoStopAfter: false });
141-
}
142-
143-
function applyPreview(editor: vscode.TextEditor, expandAbbrList: ExpandAbbreviationInput[], rangesToReplace: PreviewRangesWithContent[]): Thenable<boolean> {
144-
let totalLinesInserted = 0;
145-
146-
return editor.edit(builder => {
147-
for (let i = 0; i < rangesToReplace.length; i++) {
148-
const expandedText = expandAbbr(expandAbbrList[i]) || '';
149-
if (!expandedText) {
150-
// Failed to expand text. We already showed an error inside expandAbbr.
151-
break;
152-
}
153-
154-
const oldPreviewRange = rangesToReplace[i].previewRange;
155-
const preceedingText = editor.document.getText(new vscode.Range(oldPreviewRange.start.line, 0, oldPreviewRange.start.line, oldPreviewRange.start.character));
156-
const indentPrefix = (preceedingText.match(/^(\s*)/) || ['', ''])[1];
157-
158-
let newText = expandedText.replace(/\n/g, '\n' + indentPrefix); // Adding indentation on each line of expanded text
159-
newText = newText.replace(/\$\{[\d]*\}/g, '|'); // Removing Tabstops
160-
newText = newText.replace(/\$\{[\d]*(:[^}]*)?\}/g, (match) => { // Replacing Placeholders
161-
return match.replace(/^\$\{[\d]*:/, '').replace('}', '');
193+
function inputChanged(value: string): string {
194+
if (value !== currentValue) {
195+
currentValue = value;
196+
makeChanges(value, false).then((out) => {
197+
if (typeof out === 'boolean') {
198+
inPreview = out;
199+
}
162200
});
163-
builder.replace(oldPreviewRange, newText);
164-
165-
const expandedTextLines = newText.split('\n');
166-
const oldPreviewLines = oldPreviewRange.end.line - oldPreviewRange.start.line + 1;
167-
const newLinesInserted = expandedTextLines.length - oldPreviewLines;
168-
169-
let lastLineEnd = expandedTextLines[expandedTextLines.length - 1].length;
170-
if (expandedTextLines.length === 1) {
171-
// If the expandedText is single line, add the length of preceeding whitespace as it will not be included in line length.
172-
lastLineEnd += oldPreviewRange.start.character;
173-
}
174-
175-
rangesToReplace[i].previewRange = new vscode.Range(oldPreviewRange.start.line + totalLinesInserted, oldPreviewRange.start.character, oldPreviewRange.end.line + totalLinesInserted + newLinesInserted, lastLineEnd);
176-
totalLinesInserted += newLinesInserted;
177201
}
178-
}, { undoStopBefore: false, undoStopAfter: false });
179-
}
180-
181-
export function wrapIndividualLinesWithAbbreviation(args: any) {
182-
if (!validate(false) || !vscode.window.activeTextEditor) {
183-
return;
184-
}
185-
186-
const editor = vscode.window.activeTextEditor;
187-
if (editor.selections.length === 1 && editor.selection.isEmpty) {
188-
vscode.window.showInformationMessage('Select more than 1 line and try again.');
189-
return;
190-
}
191-
if (editor.selections.find(x => x.isEmpty)) {
192-
vscode.window.showInformationMessage('Select more than 1 line in each selection and try again.');
193-
return;
194-
}
195-
let rangesToReplace: vscode.Range[] = [];
196-
editor.selections.forEach(selection => {
197-
let rangeToReplace: vscode.Range = selection.isReversed ? new vscode.Range(selection.active, selection.anchor) : selection;
198-
if (!rangeToReplace.isSingleLine && rangeToReplace.end.character === 0) {
199-
let previousLine = rangeToReplace.end.line - 1;
200-
let lastChar = editor.document.lineAt(previousLine).text.length;
201-
rangeToReplace = new vscode.Range(rangeToReplace.start, new vscode.Position(previousLine, lastChar));
202-
}
203-
204-
rangeToReplace = ignoreExtraWhitespaceSelected(rangeToReplace, editor.document);
205-
rangesToReplace.push(rangeToReplace);
206-
});
207-
208-
const syntax = getSyntaxFromArgs({ language: editor.document.languageId });
209-
if (!syntax) {
210-
return;
202+
return '';
211203
}
212-
213-
const abbreviationPromise = (args && args['abbreviation']) ? Promise.resolve(args['abbreviation']) : vscode.window.showInputBox({ prompt: 'Enter Abbreviation' });
214-
const helper = getEmmetHelper();
215-
204+
const abbreviationPromise = (args && args['abbreviation']) ? Promise.resolve(args['abbreviation']) : vscode.window.showInputBox({ prompt: 'Enter Abbreviation', validateInput: inputChanged });
216205
return abbreviationPromise.then(inputAbbreviation => {
217-
let expandAbbrInput: ExpandAbbreviationInput[] = [];
218-
if (!inputAbbreviation || !inputAbbreviation.trim() || !helper.isAbbreviationValid(syntax, inputAbbreviation)) { return false; }
219-
220-
let extractedResults = helper.extractAbbreviationFromText(inputAbbreviation);
221-
if (!extractedResults) {
222-
return false;
223-
}
224-
rangesToReplace.forEach(rangeToReplace => {
225-
let lines = editor.document.getText(rangeToReplace).split('\n').map(x => x.trim());
226-
227-
let { abbreviation, filter } = extractedResults;
228-
let input: ExpandAbbreviationInput = {
229-
syntax,
230-
abbreviation,
231-
rangeToReplace,
232-
textToWrap: lines,
233-
filter
234-
};
235-
expandAbbrInput.push(input);
236-
});
237-
238-
return expandAbbreviationInRange(editor, expandAbbrInput, false);
206+
return makeChanges(inputAbbreviation, true);
239207
});
240-
241-
}
242-
243-
function ignoreExtraWhitespaceSelected(range: vscode.Range, document: vscode.TextDocument): vscode.Range {
244-
const firstLineOfSelection = document.lineAt(range.start).text.substr(range.start.character);
245-
const matches = firstLineOfSelection.match(/^(\s*)/);
246-
const extraWhiteSpaceSelected = matches ? matches[1].length : 0;
247-
248-
return new vscode.Range(range.start.line, range.start.character + extraWhiteSpaceSelected, range.end.line, range.end.character);
249208
}
250209

251210
export function expandEmmetAbbreviation(args: any): Thenable<boolean | undefined> {

0 commit comments

Comments
 (0)