toggleComment.ts 7.1 KB
Newer Older
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  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';
7 8
import { getNodesInBetween, getNode, getHtmlNode, parseDocument, sameNodes, isStyleSheet, validate } from './util';
import { Node, Stylesheet, Rule } from 'EmmetNode';
9 10
import parseStylesheet from '@emmetio/css-parser';
import { DocumentStreamReader } from './bufferStream';
11 12 13

const startCommentStylesheet = '/*';
const endCommentStylesheet = '*/';
14 15
const startCommentHTML = '<!-- ';
const endCommentHTML = ' -->';
16

17 18
export function toggleComment(): Thenable<boolean> | undefined {
	if (!validate() || !vscode.window.activeTextEditor) {
19 20
		return;
	}
21
	const editor = vscode.window.activeTextEditor;
22
	let rootNode = parseDocument(editor.document);
23 24 25
	if (!rootNode) {
		return;
	}
26

27
	return editor.edit(editBuilder => {
28
		let allEdits: vscode.TextEdit[][] = [];
29
		editor.selections.reverse().forEach(selection => {
30
			let edits = isStyleSheet(editor.document.languageId) ? toggleCommentStylesheet(selection, <Stylesheet>rootNode) : toggleCommentHTML(editor.document, selection, rootNode!);
31 32 33
			if (edits.length > 0) {
				allEdits.push(edits);
			}
34
		});
35 36 37 38 39 40 41

		// 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);
42
		for (const edits of allEdits) {
43 44 45 46 47 48 49
			if (edits[0].range.end.isAfterOrEqual(lastEditPosition)) {
				edits.forEach(x => {
					editBuilder.replace(x.range, x.newText);
					lastEditPosition = x.range.end;
				});
			}
		}
50 51 52
	});
}

53
function toggleCommentHTML(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): vscode.TextEdit[] {
54 55
	const selectionStart = selection.isReversed ? selection.active : selection.anchor;
	const selectionEnd = selection.isReversed ? selection.anchor : selection.active;
56

57 58
	let startNode = getHtmlNode(document, rootNode, selectionStart, true);
	let endNode = getHtmlNode(document, rootNode, selectionEnd, true);
59 60

	if (!startNode || !endNode) {
61 62 63 64 65 66 67 68 69
		return [];
	}

	if (sameNodes(startNode, endNode) && startNode.name === 'style'
		&& startNode.open.end.isBefore(selectionStart)
		&& startNode.close.start.isAfter(selectionEnd)) {
		let buffer = new DocumentStreamReader(document, startNode.open.end, new vscode.Range(startNode.open.end, startNode.close.start));
		let cssRootNode = parseStylesheet(buffer);

70
		return toggleCommentStylesheet(selection, cssRootNode);
R
Ramya Achutha Rao 已提交
71
	}
72

73
	let allNodes: Node[] = getNodesInBetween(startNode, endNode);
74
	let edits: vscode.TextEdit[] = [];
75 76

	allNodes.forEach(node => {
77
		edits = edits.concat(getRangesToUnCommentHTML(node, document));
78 79 80
	});

	if (startNode.type === 'comment') {
81
		return edits;
82 83
	}

84 85 86 87 88

	edits.push(new vscode.TextEdit(new vscode.Range(allNodes[0].start, allNodes[0].start), startCommentHTML));
	edits.push(new vscode.TextEdit(new vscode.Range(allNodes[allNodes.length - 1].end, allNodes[allNodes.length - 1].end), endCommentHTML));

	return edits;
89 90
}

91 92
function getRangesToUnCommentHTML(node: Node, document: vscode.TextDocument): vscode.TextEdit[] {
	let unCommentTextEdits: vscode.TextEdit[] = [];
93 94 95 96

	// If current node is commented, then uncomment and return
	if (node.type === 'comment') {

97 98 99 100
		unCommentTextEdits.push(new vscode.TextEdit(new vscode.Range(node.start, node.start.translate(0, startCommentHTML.length)), ''));
		unCommentTextEdits.push(new vscode.TextEdit(new vscode.Range(node.end.translate(0, -endCommentHTML.length), node.end), ''));

		return unCommentTextEdits;
101 102 103 104
	}

	// All children of current node should be uncommented
	node.children.forEach(childNode => {
105
		unCommentTextEdits = unCommentTextEdits.concat(getRangesToUnCommentHTML(childNode, document));
106 107
	});

108
	return unCommentTextEdits;
109 110
}

111
function toggleCommentStylesheet(selection: vscode.Selection, rootNode: Stylesheet): vscode.TextEdit[] {
112 113
	let selectionStart = selection.isReversed ? selection.active : selection.anchor;
	let selectionEnd = selection.isReversed ? selection.anchor : selection.active;
114

115 116
	let startNode = getNode(rootNode, selectionStart, true);
	let endNode = getNode(rootNode, selectionEnd, true);
117

118 119 120 121 122 123 124
	if (!selection.isEmpty) {
		selectionStart = adjustStartNodeCss(startNode, selectionStart, rootNode);
		selectionEnd = adjustEndNodeCss(endNode, selectionEnd, rootNode);
		selection = new vscode.Selection(selectionStart, selectionEnd);
	} else if (startNode) {
		selectionStart = startNode.start;
		selectionEnd = startNode.end;
125 126 127 128 129
		selection = new vscode.Selection(selectionStart, selectionEnd);
	}

	// Uncomment the comments that intersect with the selection.
	let rangesToUnComment: vscode.Range[] = [];
130
	let edits: vscode.TextEdit[] = [];
131 132 133 134
	rootNode.comments.forEach(comment => {
		let commentRange = new vscode.Range(comment.start, comment.end);
		if (selection.intersection(commentRange)) {
			rangesToUnComment.push(commentRange);
135 136
			edits.push(new vscode.TextEdit(new vscode.Range(comment.start, comment.start.translate(0, startCommentStylesheet.length)), ''));
			edits.push(new vscode.TextEdit(new vscode.Range(comment.end.translate(0, -endCommentStylesheet.length), comment.end), ''));
137 138 139
		}
	});

140 141 142 143 144 145 146 147 148 149
	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)
	];


150 151
}

152
function adjustStartNodeCss(node: Node | null, pos: vscode.Position, rootNode: Stylesheet): vscode.Position {
153 154
	for (const comment of rootNode.comments) {
		let commentRange = new vscode.Range(comment.start, comment.end);
155 156
		if (commentRange.contains(pos)) {
			return pos;
157
		}
158 159
	}

160 161 162
	if (!node) {
		return pos;
	}
163

164 165 166
	if (node.type === 'property') {
		return node.start;
	}
167

168 169 170 171 172 173 174 175
	const rule = <Rule>node;
	if (pos.isBefore(rule.contentStartToken.end) || !rule.firstChild) {
		return rule.start;
	}

	if (pos.isBefore(rule.firstChild.start)) {
		return pos;
	}
176

177 178 179 180
	let newStartNode = rule.firstChild;
	while (newStartNode.nextSibling && pos.isAfter(newStartNode.end)) {
		newStartNode = newStartNode.nextSibling;
	}
181

182 183
	return newStartNode.start;
}
184

185
function adjustEndNodeCss(node: Node | null, pos: vscode.Position, rootNode: Stylesheet): vscode.Position {
186 187
	for (const comment of rootNode.comments) {
		let commentRange = new vscode.Range(comment.start, comment.end);
188 189
		if (commentRange.contains(pos)) {
			return pos;
190
		}
191
	}
192

193 194 195
	if (!node) {
		return pos;
	}
196

197 198 199 200 201 202 203 204 205 206 207 208
	if (node.type === 'property') {
		return node.end;
	}

	const rule = <Rule>node;
	if (pos.isEqual(rule.contentEndToken.end) || !rule.firstChild) {
		return rule.end;
	}

	if (pos.isAfter(rule.children[rule.children.length - 1].end)) {
		return pos;
	}
209

210 211 212 213 214 215
	let newEndNode = rule.children[rule.children.length - 1];
	while (newEndNode.previousSibling && pos.isBefore(newEndNode.start)) {
		newEndNode = newEndNode.previousSibling;
	}

	return newEndNode.end;
216
}
217 218