Skip to content
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 refactor toggle comment #113557

Merged
merged 2 commits into from Dec 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
104 changes: 53 additions & 51 deletions extensions/emmet/src/toggleComment.ts
Expand Up @@ -4,10 +4,10 @@
*--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';
import { getNodesInBetween, getNode, getHtmlNode, parseDocument, sameNodes, isStyleSheet, validate } from './util';
import { Node, Stylesheet, Rule } from 'EmmetNode';
import { getNodesInBetween, getFlatNode, getHtmlFlatNode, sameNodes, isStyleSheet, validate, offsetRangeToVsRange, offsetRangeToSelection } from './util';
import { Node, Stylesheet, Rule } from 'EmmetFlatNode';
import parseStylesheet from '@emmetio/css-parser';
import { DocumentStreamReader } from './bufferStream';
import { getRootNode } from './parseDocument';

let startCommentStylesheet: string;
let endCommentStylesheet: string;
Expand All @@ -21,15 +21,15 @@ export function toggleComment(): Thenable<boolean> | undefined {
setupCommentSpacing();

const editor = vscode.window.activeTextEditor;
let rootNode = parseDocument(editor.document);
const rootNode = getRootNode(editor.document, true);
if (!rootNode) {
return;
}

return editor.edit(editBuilder => {
let allEdits: vscode.TextEdit[][] = [];
editor.selections.reverse().forEach(selection => {
let edits = isStyleSheet(editor.document.languageId) ? toggleCommentStylesheet(selection, <Stylesheet>rootNode) : toggleCommentHTML(editor.document, selection, rootNode!);
const edits = isStyleSheet(editor.document.languageId) ? toggleCommentStylesheet(editor.document, selection, <Stylesheet>rootNode) : toggleCommentHTML(editor.document, selection, rootNode!);
if (edits.length > 0) {
allEdits.push(edits);
}
Expand All @@ -55,21 +55,25 @@ export function toggleComment(): Thenable<boolean> | undefined {
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();

let startNode = getHtmlNode(document, rootNode, selectionStart, true);
let endNode = getHtmlNode(document, rootNode, selectionEnd, true);
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.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);

return toggleCommentStylesheet(selection, cssRootNode);
&& 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);
Expand All @@ -84,8 +88,8 @@ function toggleCommentHTML(document: vscode.TextDocument, selection: vscode.Sele
}


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));
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;
}
Expand All @@ -95,10 +99,8 @@ function getRangesToUnCommentHTML(node: Node, document: vscode.TextDocument): vs

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

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), ''));

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;
}

Expand All @@ -110,32 +112,34 @@ function getRangesToUnCommentHTML(node: Node, document: vscode.TextDocument): vs
return unCommentTextEdits;
}

function toggleCommentStylesheet(selection: vscode.Selection, rootNode: Stylesheet): vscode.TextEdit[] {
let selectionStart = selection.isReversed ? selection.active : selection.anchor;
let selectionEnd = selection.isReversed ? selection.anchor : selection.active;
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);

let startNode = getNode(rootNode, selectionStart, true);
let endNode = getNode(rootNode, selectionEnd, true);
const startNode = getFlatNode(rootNode, selectionStartOffset, true);
const endNode = getFlatNode(rootNode, selectionEndOffset, true);

if (!selection.isEmpty) {
selectionStart = adjustStartNodeCss(startNode, selectionStart, rootNode);
selectionEnd = adjustEndNodeCss(endNode, selectionEnd, rootNode);
selection = new vscode.Selection(selectionStart, selectionEnd);
selectionStartOffset = adjustStartNodeCss(startNode, selectionStartOffset, rootNode);
selectionEndOffset = adjustEndNodeCss(endNode, selectionEndOffset, rootNode);
selection = offsetRangeToSelection(document, selectionStartOffset, selectionEndOffset);
} else if (startNode) {
selectionStart = startNode.start;
selectionEnd = startNode.end;
selection = new vscode.Selection(selectionStart, selectionEnd);
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 => {
let commentRange = new vscode.Range(comment.start, comment.end);
const commentRange = offsetRangeToVsRange(document, comment.start, comment.end);
if (selection.intersection(commentRange)) {
rangesToUnComment.push(commentRange);
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), ''));
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), ''));
}
});

Expand Down Expand Up @@ -164,66 +168,64 @@ function setupCommentSpacing() {
}
}

function adjustStartNodeCss(node: Node | null, pos: vscode.Position, rootNode: Stylesheet): vscode.Position {
function adjustStartNodeCss(node: Node | undefined, offset: number, rootNode: Stylesheet): number {
for (const comment of rootNode.comments) {
let commentRange = new vscode.Range(comment.start, comment.end);
if (commentRange.contains(pos)) {
return pos;
if (comment.start <= offset && offset <= comment.end) {
return offset;
}
}

if (!node) {
return pos;
return offset;
}

if (node.type === 'property') {
return node.start;
}

const rule = <Rule>node;
if (pos.isBefore(rule.contentStartToken.end) || !rule.firstChild) {
if (offset < rule.contentStartToken.end || !rule.firstChild) {
return rule.start;
}

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

let newStartNode = rule.firstChild;
while (newStartNode.nextSibling && pos.isAfter(newStartNode.end)) {
while (newStartNode.nextSibling && offset > newStartNode.end) {
newStartNode = newStartNode.nextSibling;
}

return newStartNode.start;
}

function adjustEndNodeCss(node: Node | null, pos: vscode.Position, rootNode: Stylesheet): vscode.Position {
function adjustEndNodeCss(node: Node | undefined, offset: number, rootNode: Stylesheet): number {
for (const comment of rootNode.comments) {
let commentRange = new vscode.Range(comment.start, comment.end);
if (commentRange.contains(pos)) {
return pos;
if (comment.start <= offset && offset <= comment.end) {
return offset;
}
}

if (!node) {
return pos;
return offset;
}

if (node.type === 'property') {
return node.end;
}

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

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

let newEndNode = rule.children[rule.children.length - 1];
while (newEndNode.previousSibling && pos.isBefore(newEndNode.start)) {
while (newEndNode.previousSibling && offset < newEndNode.start) {
newEndNode = newEndNode.previousSibling;
}

Expand Down
30 changes: 13 additions & 17 deletions extensions/emmet/src/util.ts
Expand Up @@ -565,7 +565,7 @@ export function findPrevWord(propertyValue: string, pos: number): [number | unde
return [newSelectionStart, newSelectionEnd];
}

export function getNodesInBetween(node1: Node, node2: Node): Node[] {
export function getNodesInBetween(node1: FlatNode, node2: FlatNode): FlatNode[] {
// Same node
if (sameNodes(node1, node2)) {
return [node1];
Expand All @@ -574,50 +574,46 @@ export function getNodesInBetween(node1: Node, node2: Node): Node[] {
// Not siblings
if (!sameNodes(node1.parent, node2.parent)) {
// node2 is ancestor of node1
if (node2.start.isBefore(node1.start)) {
if (node2.start < node1.start) {
return [node2];
}

// node1 is ancestor of node2
if (node2.start.isBefore(node1.end)) {
if (node2.start < node1.end) {
return [node1];
}

// Get the highest ancestor of node1 that should be commented
while (node1.parent && node1.parent.end.isBefore(node2.start)) {
while (node1.parent && node1.parent.end < node2.start) {
node1 = node1.parent;
}

// Get the highest ancestor of node2 that should be commented
while (node2.parent && node2.parent.start.isAfter(node1.start)) {
while (node2.parent && node2.parent.start > node1.start) {
node2 = node2.parent;
}
}

const siblings: Node[] = [];
let currentNode = node1;
const siblings: FlatNode[] = [];
let currentNode: FlatNode | undefined = node1;
const position = node2.end;
while (currentNode && position.isAfter(currentNode.start)) {
while (currentNode && position > currentNode.start) {
siblings.push(currentNode);
currentNode = currentNode.nextSibling;
}
return siblings;
}

function samePositions(pos1: vscode.Position | undefined, pos2: vscode.Position | undefined): boolean {
if (!pos1 && !pos2) {
return true;
} else if (pos1 && pos2 && pos1.isEqual(pos2)) {
export function sameNodes(node1: FlatNode | undefined, node2: FlatNode | undefined): boolean {
// return true if they're both undefined
if (!node1 && !node2) {
return true;
}
return false;
}

export function sameNodes(node1: Node, node2: Node): boolean {
// return false if only one of them is undefined
if (!node1 || !node2) {
return false;
}
return samePositions(node1.start, node2.start) && samePositions(node1.end, node2.end);
return node1.start === node2.start && node1.end === node2.end;
}

export function getEmmetConfiguration(syntax: string) {
Expand Down