-
Notifications
You must be signed in to change notification settings - Fork 30.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Emmet - Wrap with abbreviation with live preview #45092
Changes from 7 commits
39e6a2b
4a566ef
4947eaf
3b9af18
57082a4
b855519
b576c20
2821c46
22ca9c4
12674c6
0889378
106bc42
5ea306b
56d3f88
9fd6348
70c0a26
206c968
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,12 @@ interface ExpandAbbreviationInput { | |
filter?: string; | ||
} | ||
|
||
interface RangesAndContent { | ||
currentRange: vscode.Range; | ||
originalRange: vscode.Range; | ||
content: string; | ||
} | ||
|
||
export function wrapWithAbbreviation(args: any) { | ||
if (!validate(false) || !vscode.window.activeTextEditor) { | ||
return; | ||
|
@@ -37,45 +43,166 @@ export function wrapWithAbbreviation(args: any) { | |
return; | ||
} | ||
|
||
const abbreviationPromise = (args && args['abbreviation']) ? Promise.resolve(args['abbreviation']) : vscode.window.showInputBox({ prompt: 'Enter Abbreviation' }); | ||
let previewMade = false; | ||
|
||
// Fetch general information for the succesive expansions. i.e. the ranges to replace and its contents | ||
let expandAbbrList: ExpandAbbreviationInput[] = []; | ||
let rangesToReplace: RangesAndContent[] = []; | ||
|
||
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) { | ||
let { active } = selection; | ||
let 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 { | ||
rangeToReplace = new vscode.Range(rangeToReplace.start.line, 0, rangeToReplace.start.line, editor.document.lineAt(rangeToReplace.start.line).text.length); | ||
} | ||
} | ||
|
||
const firstLineOfSelection = editor.document.lineAt(rangeToReplace.start).text.substr(rangeToReplace.start.character); | ||
const matches = firstLineOfSelection.match(/^(\s*)/); | ||
const preceedingWhiteSpace = matches ? matches[1].length : 0; | ||
|
||
rangeToReplace = new vscode.Range(rangeToReplace.start.line, rangeToReplace.start.character + preceedingWhiteSpace, rangeToReplace.end.line, rangeToReplace.end.character); | ||
let textToReplace = editor.document.getText(rangeToReplace); | ||
rangesToReplace.push({ currentRange: rangeToReplace, originalRange: rangeToReplace, content: textToReplace }); | ||
}); | ||
|
||
let abbreviationPromise; | ||
let currentValue = ''; | ||
|
||
function inputChanged(value: string): string { | ||
if (value !== currentValue) { | ||
currentValue = value; | ||
makeChanges(value, previewMade, false).then((out) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if |
||
if (typeof out === 'boolean') { | ||
previewMade = out; | ||
} | ||
}); | ||
} | ||
return ''; | ||
} | ||
|
||
abbreviationPromise = (args && args['abbreviation']) ? Promise.resolve(args['abbreviation']) : vscode.window.showInputBox({ prompt: 'Enter Abbreviation', validateInput: inputChanged }); | ||
const helper = getEmmetHelper(); | ||
|
||
return abbreviationPromise.then(inputAbbreviation => { | ||
if (!inputAbbreviation || !inputAbbreviation.trim() || !helper.isAbbreviationValid(syntax, inputAbbreviation)) { return false; } | ||
function makeChanges(inputAbbreviation: string | undefined, previewMade?: boolean, definitive?: boolean): Thenable<any> { | ||
if (!inputAbbreviation || !inputAbbreviation.trim() || !helper.isAbbreviationValid(syntax, inputAbbreviation)) { | ||
let returnPromise: Thenable<any> = Promise.resolve(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can condense these 5 lines to |
||
if (previewMade) { | ||
returnPromise = revertPreview(editor, rangesToReplace).then(() => { return false; }); | ||
} | ||
return returnPromise; | ||
} | ||
|
||
let extractedResults = helper.extractAbbreviationFromText(inputAbbreviation); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might be an exisitng bug... but shouldnt we check for the validity of the abbreviation after it is extracted instead of before? I would suggest to move the extraction to be the first thing that we do in
|
||
if (!extractedResults) { | ||
return false; | ||
return Promise.resolve(previewMade); | ||
} | ||
let { abbreviation, filter } = extractedResults; | ||
|
||
let expandAbbrList: ExpandAbbreviationInput[] = []; | ||
expandAbbrList = []; | ||
|
||
editor.selections.forEach(selection => { | ||
let rangeToReplace: vscode.Range = selection.isReversed ? new vscode.Range(selection.active, selection.anchor) : selection; | ||
if (rangeToReplace.isEmpty) { | ||
let { active } = selection; | ||
let 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 { | ||
rangeToReplace = new vscode.Range(rangeToReplace.start.line, 0, rangeToReplace.start.line, editor.document.lineAt(rangeToReplace.start.line).text.length); | ||
} | ||
// we need to apply the previewchanges and get the new ranges | ||
let revertPromise: Thenable<any> = Promise.resolve(); | ||
if (definitive) { | ||
if (previewMade) { | ||
revertPromise = revertPreview(editor, rangesToReplace); | ||
} | ||
rangesToReplace.forEach(rangesAndContent => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
let rangeToReplace = rangesAndContent.originalRange; | ||
let textToWrap = rangeToReplace.isSingleLine ? ['$TM_SELECTED_TEXT'] : ['\n\t$TM_SELECTED_TEXT\n']; | ||
expandAbbrList.push({ syntax: syntax || '', abbreviation, rangeToReplace, textToWrap, filter }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why would |
||
}); | ||
return revertPromise.then(() => { | ||
return expandAbbreviationInRange(editor, expandAbbrList, true).then(() => { return Promise.resolve(); }); | ||
}); | ||
} else { | ||
rangesToReplace.forEach(rangesAndContent => { | ||
let match = rangesAndContent.content.match(/\n[\s]*/g); | ||
let textToWrap = match ? ['\n\t' + rangesAndContent.content.split(match[match.length - 1]).join('\n\t') + '\n'] : [rangesAndContent.content]; | ||
expandAbbrList.push({ syntax: syntax || '', abbreviation, rangeToReplace: rangesAndContent.originalRange, textToWrap, filter }); | ||
}); | ||
} | ||
|
||
const firstLineOfSelection = editor.document.lineAt(rangeToReplace.start).text.substr(rangeToReplace.start.character); | ||
const matches = firstLineOfSelection.match(/^(\s*)/); | ||
const preceedingWhiteSpace = matches ? matches[1].length : 0; | ||
|
||
rangeToReplace = new vscode.Range(rangeToReplace.start.line, rangeToReplace.start.character + preceedingWhiteSpace, rangeToReplace.end.line, rangeToReplace.end.character); | ||
let textToWrap = rangeToReplace.isSingleLine ? ['$TM_SELECTED_TEXT'] : ['\n\t$TM_SELECTED_TEXT\n']; | ||
expandAbbrList.push({ syntax, abbreviation, rangeToReplace, textToWrap, filter }); | ||
return Promise.resolve().then(() => { | ||
return applyPreview(editor, expandAbbrList, rangesToReplace).then(ranges => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why return the ranges here from |
||
for (let i = 0; i < ranges.length; i++) { | ||
rangesToReplace[i].currentRange = ranges[i]; | ||
} | ||
previewMade = true; | ||
return previewMade; | ||
}); | ||
}); | ||
|
||
return expandAbbreviationInRange(editor, expandAbbrList, true); | ||
} | ||
|
||
// On inputBox closing | ||
return abbreviationPromise.then(inputAbbreviation => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The tests are currently failing as the promise returned by Since |
||
if (typeof inputAbbreviation === 'string') { | ||
// If succeeded (user pressed enter), remove the previews and add the final snippet | ||
makeChanges(inputAbbreviation, previewMade, true); | ||
} else { | ||
// If not (user pressed esc or changed focus), remove the previews. | ||
Promise.resolve().then(() => { | ||
let revertPromise: Thenable<any> = Promise.resolve(); | ||
if (previewMade) { | ||
revertPromise = revertPreview(editor, rangesToReplace); | ||
} | ||
return revertPromise; | ||
}); | ||
} | ||
}); | ||
} | ||
|
||
function revertPreview(editor: vscode.TextEditor, rangesToReplace: RangesAndContent[]): Thenable<any> { | ||
return editor.edit(builder => { | ||
for (let i = 0; i < rangesToReplace.length; i++) { | ||
builder.replace(rangesToReplace[i].currentRange, rangesToReplace[i].content); | ||
rangesToReplace[i].currentRange = rangesToReplace[i].originalRange; | ||
} | ||
}, { undoStopBefore: false, undoStopAfter: false }); | ||
} | ||
|
||
function applyPreview(editor: vscode.TextEditor, expandAbbrList: ExpandAbbreviationInput[], rangesToReplace: RangesAndContent[]): Thenable<vscode.Range[]> { | ||
const anyExpandAbbrInput = expandAbbrList[0]; | ||
let expandedText = expandAbbr(anyExpandAbbrInput); | ||
let rangesObtained: vscode.Range[] = []; | ||
let linesInserted = 0; | ||
|
||
if (expandedText) { | ||
return editor.edit(builder => { | ||
for (let i = 0; i < rangesToReplace.length; i++) { | ||
if (i) { | ||
expandedText = expandAbbr(expandAbbrList[i]); | ||
} | ||
let thisRange = rangesToReplace[i].currentRange; | ||
let indentPrefix = ''; | ||
let preceedingText = editor.document.getText(new vscode.Range(new vscode.Position(thisRange.start.line, 0), thisRange.start)); | ||
// If there is only whitespace before the text to wrap, take that as prefix. If not, take as much whitespace as there is before text appears. | ||
if (!preceedingText.match(/[^\s]/)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of this if/else, why not follow the same logic as |
||
indentPrefix = preceedingText; | ||
} else { | ||
indentPrefix = (preceedingText.match(/(^[\s]*)/) || ['', ''])[1]; | ||
} | ||
|
||
let newText = expandedText || ''; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why would we continue if |
||
newText = newText.replace(/\n/g, '\n' + indentPrefix).replace(/\$\{[\d]*\}/g, '|'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will not replace placeholders. Try wrapping with |
||
let newTextLines = (newText.match(/\n/g) || []).length + 1; | ||
let currentTextLines = thisRange.end.line - thisRange.start.line + 1; | ||
builder.replace(thisRange, newText); | ||
rangesObtained.push(new vscode.Range(thisRange.start.line + linesInserted, thisRange.start.character, thisRange.start.line + linesInserted + newTextLines - 1, 1000)); // TODO: fix 1000! Count characters in the resulting text? | ||
linesInserted += newTextLines - currentTextLines; | ||
} | ||
}, { undoStopBefore: false, undoStopAfter: false }).then(() => { | ||
return rangesObtained; | ||
}); | ||
} | ||
return Promise.resolve([]); | ||
} | ||
|
||
export function wrapIndividualLinesWithAbbreviation(args: any) { | ||
if (!validate(false) || !vscode.window.activeTextEditor) { | ||
return; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
currentRange
here will only have the preview ranges correct? If so, let's rename it topreviewRange
to make it more readable.Also rename
content
tooriginalContent
to match with theoriginalRange
andRangesAndContent
toPreviewRangesWithContent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure