eae5d8c807
These conflicts will be resolved in the following commits. We do it this way so that PR review is possible.
236 lines
8.0 KiB
TypeScript
236 lines
8.0 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import * as vscode from 'vscode';
|
|
import { getNodesInBetween, getFlatNode, getHtmlFlatNode, sameNodes, isStyleSheet, validate, offsetRangeToVsRange, offsetRangeToSelection } from './util';
|
|
import { Node, Stylesheet, Rule } from 'EmmetFlatNode';
|
|
import parseStylesheet from '@emmetio/css-parser';
|
|
import { getRootNode } from './parseDocument';
|
|
|
|
let startCommentStylesheet: string;
|
|
let endCommentStylesheet: string;
|
|
let startCommentHTML: string;
|
|
let endCommentHTML: string;
|
|
|
|
export function toggleComment(): Thenable<boolean> | undefined {
|
|
if (!validate() || !vscode.window.activeTextEditor) {
|
|
return;
|
|
}
|
|
setupCommentSpacing();
|
|
|
|
const editor = vscode.window.activeTextEditor;
|
|
const rootNode = getRootNode(editor.document, true);
|
|
if (!rootNode) {
|
|
return;
|
|
}
|
|
|
|
return editor.edit(editBuilder => {
|
|
let allEdits: vscode.TextEdit[][] = [];
|
|
editor.selections.reverse().forEach(selection => {
|
|
const edits = isStyleSheet(editor.document.languageId) ? toggleCommentStylesheet(editor.document, selection, <Stylesheet>rootNode) : toggleCommentHTML(editor.document, selection, rootNode!);
|
|
if (edits.length > 0) {
|
|
allEdits.push(edits);
|
|
}
|
|
});
|
|
|
|
// Apply edits in order so we can skip nested ones.
|
|
allEdits.sort((arr1, arr2) => {
|
|
let result = arr1[0].range.start.line - arr2[0].range.start.line;
|
|
return result === 0 ? arr1[0].range.start.character - arr2[0].range.start.character : result;
|
|
});
|
|
let lastEditPosition = new vscode.Position(0, 0);
|
|
for (const edits of allEdits) {
|
|
if (edits[0].range.end.isAfterOrEqual(lastEditPosition)) {
|
|
edits.forEach(x => {
|
|
editBuilder.replace(x.range, x.newText);
|
|
lastEditPosition = x.range.end;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function toggleCommentHTML(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): vscode.TextEdit[] {
|
|
const selectionStart = selection.isReversed ? selection.active : selection.anchor;
|
|
const selectionEnd = selection.isReversed ? selection.anchor : selection.active;
|
|
const selectionStartOffset = document.offsetAt(selectionStart);
|
|
const selectionEndOffset = document.offsetAt(selectionEnd);
|
|
const documentText = document.getText();
|
|
|
|
const startNode = getHtmlFlatNode(documentText, rootNode, selectionStartOffset, true);
|
|
const endNode = getHtmlFlatNode(documentText, rootNode, selectionEndOffset, true);
|
|
|
|
if (!startNode || !endNode) {
|
|
return [];
|
|
}
|
|
|
|
if (sameNodes(startNode, endNode) && startNode.name === 'style'
|
|
&& startNode.open && startNode.close
|
|
&& startNode.open.end < selectionStartOffset
|
|
&& startNode.close.start > selectionEndOffset) {
|
|
const buffer = ' '.repeat(startNode.open.end) +
|
|
documentText.substring(startNode.open.end, startNode.close.start);
|
|
const cssRootNode = parseStylesheet(buffer);
|
|
return toggleCommentStylesheet(document, selection, cssRootNode);
|
|
}
|
|
|
|
let allNodes: Node[] = getNodesInBetween(startNode, endNode);
|
|
let edits: vscode.TextEdit[] = [];
|
|
|
|
allNodes.forEach(node => {
|
|
edits = edits.concat(getRangesToUnCommentHTML(node, document));
|
|
});
|
|
|
|
if (startNode.type === 'comment') {
|
|
return edits;
|
|
}
|
|
|
|
|
|
edits.push(new vscode.TextEdit(offsetRangeToVsRange(document, allNodes[0].start, allNodes[0].start), startCommentHTML));
|
|
edits.push(new vscode.TextEdit(offsetRangeToVsRange(document, allNodes[allNodes.length - 1].end, allNodes[allNodes.length - 1].end), endCommentHTML));
|
|
|
|
return edits;
|
|
}
|
|
|
|
function getRangesToUnCommentHTML(node: Node, document: vscode.TextDocument): vscode.TextEdit[] {
|
|
let unCommentTextEdits: vscode.TextEdit[] = [];
|
|
|
|
// If current node is commented, then uncomment and return
|
|
if (node.type === 'comment') {
|
|
unCommentTextEdits.push(new vscode.TextEdit(offsetRangeToVsRange(document, node.start, node.start + startCommentHTML.length), ''));
|
|
unCommentTextEdits.push(new vscode.TextEdit(offsetRangeToVsRange(document, node.end - endCommentHTML.length, node.end), ''));
|
|
return unCommentTextEdits;
|
|
}
|
|
|
|
// All children of current node should be uncommented
|
|
node.children.forEach(childNode => {
|
|
unCommentTextEdits = unCommentTextEdits.concat(getRangesToUnCommentHTML(childNode, document));
|
|
});
|
|
|
|
return unCommentTextEdits;
|
|
}
|
|
|
|
function toggleCommentStylesheet(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Stylesheet): vscode.TextEdit[] {
|
|
const selectionStart = selection.isReversed ? selection.active : selection.anchor;
|
|
const selectionEnd = selection.isReversed ? selection.anchor : selection.active;
|
|
let selectionStartOffset = document.offsetAt(selectionStart);
|
|
let selectionEndOffset = document.offsetAt(selectionEnd);
|
|
|
|
const startNode = getFlatNode(rootNode, selectionStartOffset, true);
|
|
const endNode = getFlatNode(rootNode, selectionEndOffset, true);
|
|
|
|
if (!selection.isEmpty) {
|
|
selectionStartOffset = adjustStartNodeCss(startNode, selectionStartOffset, rootNode);
|
|
selectionEndOffset = adjustEndNodeCss(endNode, selectionEndOffset, rootNode);
|
|
selection = offsetRangeToSelection(document, selectionStartOffset, selectionEndOffset);
|
|
} else if (startNode) {
|
|
selectionStartOffset = startNode.start;
|
|
selectionEndOffset = startNode.end;
|
|
selection = offsetRangeToSelection(document, selectionStartOffset, selectionEndOffset);
|
|
}
|
|
|
|
// Uncomment the comments that intersect with the selection.
|
|
let rangesToUnComment: vscode.Range[] = [];
|
|
let edits: vscode.TextEdit[] = [];
|
|
rootNode.comments.forEach(comment => {
|
|
const commentRange = offsetRangeToVsRange(document, comment.start, comment.end);
|
|
if (selection.intersection(commentRange)) {
|
|
rangesToUnComment.push(commentRange);
|
|
edits.push(new vscode.TextEdit(offsetRangeToVsRange(document, comment.start, comment.start + startCommentStylesheet.length), ''));
|
|
edits.push(new vscode.TextEdit(offsetRangeToVsRange(document, comment.end - endCommentStylesheet.length, comment.end), ''));
|
|
}
|
|
});
|
|
|
|
if (edits.length > 0) {
|
|
return edits;
|
|
}
|
|
|
|
return [
|
|
new vscode.TextEdit(new vscode.Range(selection.start, selection.start), startCommentStylesheet),
|
|
new vscode.TextEdit(new vscode.Range(selection.end, selection.end), endCommentStylesheet)
|
|
];
|
|
}
|
|
|
|
function setupCommentSpacing() {
|
|
const config: boolean | undefined = vscode.workspace.getConfiguration('editor.comments').get('insertSpace');
|
|
if (config) {
|
|
startCommentStylesheet = '/* ';
|
|
endCommentStylesheet = ' */';
|
|
startCommentHTML = '<!-- ';
|
|
endCommentHTML = ' -->';
|
|
} else {
|
|
startCommentStylesheet = '/*';
|
|
endCommentStylesheet = '*/';
|
|
startCommentHTML = '<!--';
|
|
endCommentHTML = '-->';
|
|
}
|
|
}
|
|
|
|
function adjustStartNodeCss(node: Node | undefined, offset: number, rootNode: Stylesheet): number {
|
|
for (const comment of rootNode.comments) {
|
|
if (comment.start <= offset && offset <= comment.end) {
|
|
return offset;
|
|
}
|
|
}
|
|
|
|
if (!node) {
|
|
return offset;
|
|
}
|
|
|
|
if (node.type === 'property') {
|
|
return node.start;
|
|
}
|
|
|
|
const rule = <Rule>node;
|
|
if (offset < rule.contentStartToken.end || !rule.firstChild) {
|
|
return rule.start;
|
|
}
|
|
|
|
if (offset < rule.firstChild.start) {
|
|
return offset;
|
|
}
|
|
|
|
let newStartNode = rule.firstChild;
|
|
while (newStartNode.nextSibling && offset > newStartNode.end) {
|
|
newStartNode = newStartNode.nextSibling;
|
|
}
|
|
|
|
return newStartNode.start;
|
|
}
|
|
|
|
function adjustEndNodeCss(node: Node | undefined, offset: number, rootNode: Stylesheet): number {
|
|
for (const comment of rootNode.comments) {
|
|
if (comment.start <= offset && offset <= comment.end) {
|
|
return offset;
|
|
}
|
|
}
|
|
|
|
if (!node) {
|
|
return offset;
|
|
}
|
|
|
|
if (node.type === 'property') {
|
|
return node.end;
|
|
}
|
|
|
|
const rule = <Rule>node;
|
|
if (offset === rule.contentEndToken.end || !rule.firstChild) {
|
|
return rule.end;
|
|
}
|
|
|
|
if (offset > rule.children[rule.children.length - 1].end) {
|
|
return offset;
|
|
}
|
|
|
|
let newEndNode = rule.children[rule.children.length - 1];
|
|
while (newEndNode.previousSibling && offset < newEndNode.start) {
|
|
newEndNode = newEndNode.previousSibling;
|
|
}
|
|
|
|
return newEndNode.end;
|
|
}
|
|
|
|
|