Skip to content

Commit

Permalink
Support joinCDATALines setting with experimental formatter
Browse files Browse the repository at this point in the history
Signed-off-by: Jessica He <jhe@redhat.com>
  • Loading branch information
JessicaJHee authored and datho7561 committed Sep 14, 2022
1 parent de9c702 commit 0dad3b2
Show file tree
Hide file tree
Showing 4 changed files with 288 additions and 48 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*******************************************************************************
* Copyright (c) 2022 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.services.format;

import java.util.List;

import org.eclipse.lemminx.dom.DOMCDATASection;
import org.eclipse.lsp4j.TextEdit;

/**
* DOM CDATA section formatter.
*/
public class DOMCDATAFormatter {
private final XMLFormatterDocumentNew formatterDocument;

public DOMCDATAFormatter(XMLFormatterDocumentNew formatterDocument) {
this.formatterDocument = formatterDocument;
}

public void formatCDATASection(DOMCDATASection cDATANode, XMLFormattingConstraints parentConstraints,
List<TextEdit> edits) {

String text = formatterDocument.getText();
int availableLineWidth = parentConstraints.getAvailableLineWidth();
int start = cDATANode.getStart();
int leftWhitespaceOffset = start > 0 ? start - 1 : 0;
int spaceStart = -1;
int spaceEnd = -1;

while (leftWhitespaceOffset > 0 && Character.isWhitespace(text.charAt(leftWhitespaceOffset))) {
leftWhitespaceOffset--;
}

if (isJoinCDATALines()) {
int contentEnd = -1;
int cDATAStartContent = cDATANode.getStartContent();
int cDATAEndContent = cDATANode.getEndContent();
for (int i = cDATAStartContent; i <= cDATAEndContent; i++) {
char c = text.charAt(i);
if (Character.isWhitespace(c)) {
// Whitespaces
if (spaceStart == -1) {
spaceStart = i;
} else {
spaceEnd = i;
}
} else {
int contentStart = i;
while (i < cDATAEndContent && !Character.isWhitespace(text.charAt(i + 1))) {
i++;
}
contentEnd = i;
availableLineWidth -= (contentEnd + 1 - contentStart);
if (availableLineWidth <= 0) {
if (spaceStart != -1) {
// Add new line when the comment extends over the maximum line width
replaceLeftSpacesWithIndentation(parentConstraints.getIndentLevel(), contentStart,
true, edits);
int indentSpaces = (getTabSize() * parentConstraints.getIndentLevel());
availableLineWidth = getMaxLineWidth() - indentSpaces - (contentEnd + 1 - contentStart);
}
} else if (spaceStart == cDATAStartContent || contentEnd == cDATAEndContent) {
// Remove spaces before and after the start and ending bracket of content
removeLeftSpaces(spaceStart + 1, contentStart, edits);
spaceStart = -1;
spaceEnd = -1;
} else {
// Normalize space between content
replaceSpacesWithOneSpace(spaceStart, spaceEnd, edits);
availableLineWidth--;
spaceStart = -1;
spaceEnd = -1;
}
}
}
}
}

private void removeLeftSpaces(int from, int to, List<TextEdit> edits) {
formatterDocument.removeLeftSpaces(from, to, edits);
}

private boolean isJoinCDATALines() {
return formatterDocument.getSharedSettings().getFormattingSettings().isJoinCDATALines();
}

private int getTabSize() {
return formatterDocument.getSharedSettings().getFormattingSettings().getTabSize();
}

private int getMaxLineWidth() {
return formatterDocument.getMaxLineWidth();
}

private void replaceSpacesWithOneSpace(int spaceStart, int spaceEnd, List<TextEdit> edits) {
formatterDocument.replaceSpacesWithOneSpace(spaceStart, spaceEnd, edits);
}

private int replaceLeftSpacesWithIndentation(int indentLevel, int offset, boolean addLineSeparator,
List<TextEdit> edits) {
return formatterDocument.replaceLeftSpacesWithIndentation(indentLevel, offset, addLineSeparator, edits);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.commons.TextDocument;
import org.eclipse.lemminx.dom.DOMAttr;
import org.eclipse.lemminx.dom.DOMCDATASection;
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMDocumentType;
import org.eclipse.lemminx.dom.DOMElement;
Expand Down Expand Up @@ -72,6 +73,8 @@ public class XMLFormatterDocumentNew {

private final DOMCommentFormatter commentFormatter;

private final DOMCDATAFormatter cDATAFormatter;

private final Collection<IFormatterParticipant> formatterParticipants;

private int startOffset = -1;
Expand Down Expand Up @@ -103,8 +106,7 @@ public XMLFormatterDocumentNew(DOMDocument xmlDocument, Range range, SharedSetti
this.processingInstructionFormatter = new DOMProcessingInstructionFormatter(this, attributeFormatter);
this.textFormatter = new DOMTextFormatter(this);
this.commentFormatter = new DOMCommentFormatter(this);


this.cDATAFormatter = new DOMCDATAFormatter(this);
}

private static String computeLineDelimiter(TextDocument textDocument) {
Expand Down Expand Up @@ -303,6 +305,11 @@ private void format(DOMNode child, XMLFormattingConstraints parentConstraints, i
commentFormatter.formatComment(commentNode, parentConstraints, edits);
break;

case Node.CDATA_SECTION_NODE:
DOMCDATASection cDATANode = (DOMCDATASection) child;
cDATAFormatter.formatCDATASection(cDATANode, parentConstraints, edits);
break;

default:
// unknown, so just leave alone for now but make sure to update
// available line width
Expand Down Expand Up @@ -446,7 +453,7 @@ public int getOffsetWithPreserveLineBreaks(int from, int to, int tabSize, boolea
}
} else if (text.charAt(i) == ' ' && StringUtils.isQuote(text.charAt(i - 1))) {
int j = 1;
while (text.charAt(i + j)== ' ') {
while (text.charAt(i + j) == ' ') {
to++;
j++;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -699,51 +699,6 @@ public void testCommentWithRange() throws BadLocationException {
assertFormat(content, expected);
}

// ---------- Tests for CDATA formatting

@Test
public void testCDATANotClosed() throws BadLocationException {
String content = "<foo>\r\n" + //
" <![CDATA[ \r\n" + //
"</foo>";
String expected = content;
assertFormat(content, expected);
}

@Disabled
@Test
public void testCDATAWithRange() throws BadLocationException {
String content = "<foo>\r\n" + //
" <![CDATA[ |<bar>|\r\n" + //
" </bar>\r\n" + //
" ]]>\r\n" + //
"</foo>";
String expected = "<foo>\r\n" + //
" <![CDATA[ <bar>\r\n" + //
" </bar>\r\n" + //
" ]]>\r\n" + //
"</foo>";
assertFormat(content, expected);
}

@Disabled
@Test
public void testJoinCDATALines() throws BadLocationException {
String content = "<a>" + lineSeparator() + //
"<![CDATA[" + lineSeparator() + //
"line 1" + lineSeparator() + //
"" + lineSeparator() + //
"" + lineSeparator() + //
"line 2" + lineSeparator() + //
"line 3" + lineSeparator() + //
"]]> </a>";
String expected = "<a>" + lineSeparator() + //
" <![CDATA[line 1 line 2 line 3]]>" + lineSeparator() + //
"</a>";
SharedSettings settings = new SharedSettings();
settings.getFormattingSettings().setJoinCDATALines(true);
assertFormat(content, expected, settings);
}

// ---------- Tests for Text formatting

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*******************************************************************************
* Copyright (c) 2022 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.services.format.experimental;

import static org.eclipse.lemminx.XMLAssert.te;

import org.eclipse.lemminx.XMLAssert;
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.settings.SharedSettings;
import org.eclipse.lsp4j.TextEdit;
import org.junit.jupiter.api.Test;

/**
* XML experimental formatter services tests with join CDATA lines setting.
*
*/
public class XMLFormatterJoinCDATALinesTest {

@Test
public void testCDATANotClosed() throws BadLocationException {
String content = "<foo>\r\n" + //
"<![CDATA[ \r\n" + //
"</foo>";
SharedSettings settings = new SharedSettings();
String expected = content;
assertFormat(content, expected, settings);
}

@Test
public void testCDATAWithRange() throws BadLocationException {
String content = "<foo>\r\n" + //
" <![CDATA[ |<bar>|\r\n" + //
" </bar>\r\n" + //
" ]]>\r\n" + //
"</foo>";
String expected = "<foo>\r\n" + //
" <![CDATA[ <bar>\r\n" + //
" </bar>\r\n" + //
" ]]>\r\n" + //
"</foo>";
SharedSettings settings = new SharedSettings();
assertFormat(content, expected, settings);
assertFormat(expected, expected, settings);
}

@Test
public void testJoinCDATALinesSameLine() throws BadLocationException {
String content = "<a> <![CDATA[\r\n" + //
" < \r\n" + //
"]]> </a>";
String expected = "<a> <![CDATA[<]]> </a>";
SharedSettings settings = new SharedSettings();
settings.getFormattingSettings().setJoinCDATALines(true);
assertFormat(content, expected, settings, //
te(0, 15, 1, 2, ""), //
te(1, 3, 2, 0, ""));
assertFormat(expected, expected, settings);
}

@Test
public void testJoinCDATALinesEmpty() throws BadLocationException {
String content = "<a> <![CDATA[\r\n" + //
" \r\n" + //
"]]> </a>";
String expected = "<a> <![CDATA[]]> </a>";
SharedSettings settings = new SharedSettings();
settings.getFormattingSettings().setJoinCDATALines(true);
assertFormat(content, expected, settings, //
te(0, 15, 2, 0, ""));
assertFormat(expected, expected, settings);
}

// From issue: https://github.com/eclipse/lemminx/issues/1193
@Test
public void testJoinCDATALinesWithText() throws BadLocationException {
String content = "<a> x <![CDATA[\r\n" + //
"<\r\n" + //
"]]> y </a>";
String expected = "<a> x <![CDATA[<]]> y </a>";
SharedSettings settings = new SharedSettings();
settings.getFormattingSettings().setJoinCDATALines(true);
assertFormat(content, expected, settings, //
te(0, 3, 0, 5, " "), //
te(0, 6, 0, 8, " "), //
te(0, 17, 1, 0, ""), //
te(1, 1, 2, 0, ""), //
te(2, 5, 2, 7, " "));
assertFormat(expected, expected, settings);
}

@Test
public void testJoinCDATALines() throws BadLocationException {
String content = "<a>\r\n" + //
"<![CDATA[\r\n" + //
"line 1\r\n" + //
"\r\n" + //
"\r\n" + //
"line 2\r\n" + //
"line 3\r\n" + //
"]]> </a>";
String expected = "<a>\r\n" + //
"<![CDATA[line 1 line 2 line 3]]> </a>";
SharedSettings settings = new SharedSettings();
settings.getFormattingSettings().setJoinCDATALines(true);
assertFormat(content, expected, settings, //
te(1, 9, 2, 0, ""), //
te(2, 6, 5, 0, " "), //
te(5, 6, 6, 0, " "), //
te(6, 6, 7, 0, ""));
assertFormat(expected, expected, settings);
}

@Test
public void testJoinCDATALinesMultiLine() throws BadLocationException {
String content = "<a>\r\n" + //
"<![CDATA[\r\n" + //
"line 1\r\n" + //
"\r\n" + //
"\r\n" + //
"line 2\r\n" + //
"line 3 test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test\r\n"
+ //
"]]> </a>";
String expected = "<a>\r\n" + //
"<![CDATA[line 1 line 2 line 3 test test test test test test test test test test test\r\n" + //
" test test test test test test test test test test test test test test test\r\n" + //
" test test test test test test test test test]]> </a>";
SharedSettings settings = new SharedSettings();
settings.getFormattingSettings().setJoinCDATALines(true);
assertFormat(content, expected, settings, //
te(1, 9, 2, 0, ""), //
te(2, 6, 5, 0, " "), //
te(5, 6, 6, 0, " "), //
te(6, 61, 6, 62, "\r\n "), //
te(6, 136, 6, 137, "\r\n "), //
te(6, 181, 7, 0, ""));
assertFormat(expected, expected, settings);
}


private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings,
TextEdit... expectedEdits) throws BadLocationException {
assertFormat(unformatted, expected, sharedSettings, "test://test.html", expectedEdits);
}

private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri,
TextEdit... expectedEdits) throws BadLocationException {
assertFormat(unformatted, expected, sharedSettings, uri, true, expectedEdits);
}

private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri,
Boolean considerRangeFormat, TextEdit... expectedEdits) throws BadLocationException {
// Force to "experimental" formatter
sharedSettings.getFormattingSettings().setExperimental(true);
XMLAssert.assertFormat(null, unformatted, expected, sharedSettings, uri, considerRangeFormat, expectedEdits);
}
}

0 comments on commit 0dad3b2

Please sign in to comment.