Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/emmet/src/toggleComment.ts
4772 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as vscode from 'vscode';
7
import { getNodesInBetween, getFlatNode, getHtmlFlatNode, sameNodes, isStyleSheet, validate, offsetRangeToVsRange, offsetRangeToSelection } from './util';
8
import { Node, Stylesheet, Rule } from 'EmmetFlatNode';
9
import parseStylesheet from '@emmetio/css-parser';
10
import { getRootNode } from './parseDocument';
11
12
let startCommentStylesheet: string;
13
let endCommentStylesheet: string;
14
let startCommentHTML: string;
15
let endCommentHTML: string;
16
17
export function toggleComment(): Thenable<boolean> | undefined {
18
if (!validate() || !vscode.window.activeTextEditor) {
19
return;
20
}
21
setupCommentSpacing();
22
23
const editor = vscode.window.activeTextEditor;
24
const rootNode = getRootNode(editor.document, true);
25
if (!rootNode) {
26
return;
27
}
28
29
return editor.edit(editBuilder => {
30
const allEdits: vscode.TextEdit[][] = [];
31
Array.from(editor.selections).reverse().forEach(selection => {
32
const edits = isStyleSheet(editor.document.languageId) ? toggleCommentStylesheet(editor.document, selection, <Stylesheet>rootNode) : toggleCommentHTML(editor.document, selection, rootNode!);
33
if (edits.length > 0) {
34
allEdits.push(edits);
35
}
36
});
37
38
// Apply edits in order so we can skip nested ones.
39
allEdits.sort((arr1, arr2) => {
40
const result = arr1[0].range.start.line - arr2[0].range.start.line;
41
return result === 0 ? arr1[0].range.start.character - arr2[0].range.start.character : result;
42
});
43
let lastEditPosition = new vscode.Position(0, 0);
44
for (const edits of allEdits) {
45
if (edits[0].range.end.isAfterOrEqual(lastEditPosition)) {
46
edits.forEach(x => {
47
editBuilder.replace(x.range, x.newText);
48
lastEditPosition = x.range.end;
49
});
50
}
51
}
52
});
53
}
54
55
function toggleCommentHTML(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): vscode.TextEdit[] {
56
const selectionStart = selection.isReversed ? selection.active : selection.anchor;
57
const selectionEnd = selection.isReversed ? selection.anchor : selection.active;
58
const selectionStartOffset = document.offsetAt(selectionStart);
59
const selectionEndOffset = document.offsetAt(selectionEnd);
60
const documentText = document.getText();
61
62
const startNode = getHtmlFlatNode(documentText, rootNode, selectionStartOffset, true);
63
const endNode = getHtmlFlatNode(documentText, rootNode, selectionEndOffset, true);
64
65
if (!startNode || !endNode) {
66
return [];
67
}
68
69
if (sameNodes(startNode, endNode) && startNode.name === 'style'
70
&& startNode.open && startNode.close
71
&& startNode.open.end < selectionStartOffset
72
&& startNode.close.start > selectionEndOffset) {
73
const buffer = ' '.repeat(startNode.open.end) +
74
documentText.substring(startNode.open.end, startNode.close.start);
75
const cssRootNode = parseStylesheet(buffer);
76
return toggleCommentStylesheet(document, selection, cssRootNode);
77
}
78
79
const allNodes: Node[] = getNodesInBetween(startNode, endNode);
80
let edits: vscode.TextEdit[] = [];
81
82
allNodes.forEach(node => {
83
edits = edits.concat(getRangesToUnCommentHTML(node, document));
84
});
85
86
if (startNode.type === 'comment') {
87
return edits;
88
}
89
90
91
edits.push(new vscode.TextEdit(offsetRangeToVsRange(document, allNodes[0].start, allNodes[0].start), startCommentHTML));
92
edits.push(new vscode.TextEdit(offsetRangeToVsRange(document, allNodes[allNodes.length - 1].end, allNodes[allNodes.length - 1].end), endCommentHTML));
93
94
return edits;
95
}
96
97
function getRangesToUnCommentHTML(node: Node, document: vscode.TextDocument): vscode.TextEdit[] {
98
let unCommentTextEdits: vscode.TextEdit[] = [];
99
100
// If current node is commented, then uncomment and return
101
if (node.type === 'comment') {
102
unCommentTextEdits.push(new vscode.TextEdit(offsetRangeToVsRange(document, node.start, node.start + startCommentHTML.length), ''));
103
unCommentTextEdits.push(new vscode.TextEdit(offsetRangeToVsRange(document, node.end - endCommentHTML.length, node.end), ''));
104
return unCommentTextEdits;
105
}
106
107
// All children of current node should be uncommented
108
node.children.forEach(childNode => {
109
unCommentTextEdits = unCommentTextEdits.concat(getRangesToUnCommentHTML(childNode, document));
110
});
111
112
return unCommentTextEdits;
113
}
114
115
function toggleCommentStylesheet(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Stylesheet): vscode.TextEdit[] {
116
const selectionStart = selection.isReversed ? selection.active : selection.anchor;
117
const selectionEnd = selection.isReversed ? selection.anchor : selection.active;
118
let selectionStartOffset = document.offsetAt(selectionStart);
119
let selectionEndOffset = document.offsetAt(selectionEnd);
120
121
const startNode = getFlatNode(rootNode, selectionStartOffset, true);
122
const endNode = getFlatNode(rootNode, selectionEndOffset, true);
123
124
if (!selection.isEmpty) {
125
selectionStartOffset = adjustStartNodeCss(startNode, selectionStartOffset, rootNode);
126
selectionEndOffset = adjustEndNodeCss(endNode, selectionEndOffset, rootNode);
127
selection = offsetRangeToSelection(document, selectionStartOffset, selectionEndOffset);
128
} else if (startNode) {
129
selectionStartOffset = startNode.start;
130
selectionEndOffset = startNode.end;
131
selection = offsetRangeToSelection(document, selectionStartOffset, selectionEndOffset);
132
}
133
134
// Uncomment the comments that intersect with the selection.
135
const rangesToUnComment: vscode.Range[] = [];
136
const edits: vscode.TextEdit[] = [];
137
rootNode.comments.forEach(comment => {
138
const commentRange = offsetRangeToVsRange(document, comment.start, comment.end);
139
if (selection.intersection(commentRange)) {
140
rangesToUnComment.push(commentRange);
141
edits.push(new vscode.TextEdit(offsetRangeToVsRange(document, comment.start, comment.start + startCommentStylesheet.length), ''));
142
edits.push(new vscode.TextEdit(offsetRangeToVsRange(document, comment.end - endCommentStylesheet.length, comment.end), ''));
143
}
144
});
145
146
if (edits.length > 0) {
147
return edits;
148
}
149
150
return [
151
new vscode.TextEdit(new vscode.Range(selection.start, selection.start), startCommentStylesheet),
152
new vscode.TextEdit(new vscode.Range(selection.end, selection.end), endCommentStylesheet)
153
];
154
}
155
156
function setupCommentSpacing() {
157
const config: boolean | undefined = vscode.workspace.getConfiguration('editor.comments').get('insertSpace');
158
if (config) {
159
startCommentStylesheet = '/* ';
160
endCommentStylesheet = ' */';
161
startCommentHTML = '<!-- ';
162
endCommentHTML = ' -->';
163
} else {
164
startCommentStylesheet = '/*';
165
endCommentStylesheet = '*/';
166
startCommentHTML = '<!--';
167
endCommentHTML = '-->';
168
}
169
}
170
171
function adjustStartNodeCss(node: Node | undefined, offset: number, rootNode: Stylesheet): number {
172
for (const comment of rootNode.comments) {
173
if (comment.start <= offset && offset <= comment.end) {
174
return offset;
175
}
176
}
177
178
if (!node) {
179
return offset;
180
}
181
182
if (node.type === 'property') {
183
return node.start;
184
}
185
186
const rule = <Rule>node;
187
if (offset < rule.contentStartToken.end || !rule.firstChild) {
188
return rule.start;
189
}
190
191
if (offset < rule.firstChild.start) {
192
return offset;
193
}
194
195
let newStartNode = rule.firstChild;
196
while (newStartNode.nextSibling && offset > newStartNode.end) {
197
newStartNode = newStartNode.nextSibling;
198
}
199
200
return newStartNode.start;
201
}
202
203
function adjustEndNodeCss(node: Node | undefined, offset: number, rootNode: Stylesheet): number {
204
for (const comment of rootNode.comments) {
205
if (comment.start <= offset && offset <= comment.end) {
206
return offset;
207
}
208
}
209
210
if (!node) {
211
return offset;
212
}
213
214
if (node.type === 'property') {
215
return node.end;
216
}
217
218
const rule = <Rule>node;
219
if (offset === rule.contentEndToken.end || !rule.firstChild) {
220
return rule.end;
221
}
222
223
if (offset > rule.children[rule.children.length - 1].end) {
224
return offset;
225
}
226
227
let newEndNode = rule.children[rule.children.length - 1];
228
while (newEndNode.previousSibling && offset < newEndNode.start) {
229
newEndNode = newEndNode.previousSibling;
230
}
231
232
return newEndNode.end;
233
}
234
235
236
237