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

Support xml.format.enforceQuoteStyle setting with experimental formatter #1249

Merged
merged 1 commit into from
Jul 14, 2022
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

import org.eclipse.lemminx.dom.DOMAttr;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lemminx.settings.EnforceQuoteStyle;
import org.eclipse.lemminx.utils.StringUtils;

/**
* DOM attribute formatter.
Expand Down Expand Up @@ -77,6 +79,24 @@ public void formatAttribute(DOMAttr attr, int prevOffset, boolean singleAttribut
removeLeftSpaces(delimiterOffset, attrValueStart, edits);
}
}

// replace current quote with preferred quote in case of attribute value
// ex: if preferred quote is single quote (')
// <a name="value"> </a>
// --> <a name='value'> </a>
String originalValue = attr.getOriginalValue();
if (getEnforceQuoteStyle() == EnforceQuoteStyle.preferred && originalValue != null) {
if (originalValue.charAt(0) != getQuotationAsChar()
JessicaJHee marked this conversation as resolved.
Show resolved Hide resolved
&& StringUtils.isQuote(originalValue.charAt(0))) {
formatterDocument.replaceQuoteWithPreferred(attr.getNodeAttrValue().getStart(),
attr.getNodeAttrValue().getStart() + 1, getQuotationAsString(), edits);
}
if (originalValue.charAt(originalValue.length() - 1) != getQuotationAsChar()
&& StringUtils.isQuote(originalValue.charAt(originalValue.length() - 1))) {
formatterDocument.replaceQuoteWithPreferred(attr.getNodeAttrValue().getEnd() - 1,
attr.getNodeAttrValue().getEnd(), getQuotationAsString(), edits);
}
}
}

private void replaceLeftSpacesWithOneSpace(int from, int to, List<TextEdit> edits) {
Expand Down Expand Up @@ -108,4 +128,15 @@ private boolean hasLineBreak(int prevOffset, int start) {
return formatterDocument.hasLineBreak(prevOffset, start);
}

private char getQuotationAsChar() {
return formatterDocument.getSharedSettings().getPreferences().getQuotationAsChar();
}

private String getQuotationAsString() {
return formatterDocument.getSharedSettings().getPreferences().getQuotationAsString();
}

private EnforceQuoteStyle getEnforceQuoteStyle() {
return formatterDocument.getSharedSettings().getFormattingSettings().getEnforceQuoteStyle();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import org.eclipse.lemminx.dom.DTDDeclParameter;
import org.eclipse.lsp4j.TextEdit;
import org.w3c.dom.Node;
import org.eclipse.lemminx.settings.EnforceQuoteStyle;
import org.eclipse.lemminx.utils.StringUtils;

/**
* DOM docType formatter.
Expand Down Expand Up @@ -52,21 +54,38 @@ public void formatDocType(DOMDocumentType docType, XMLFormattingConstraints pare
constraints.setIndentLevel(constraints.getIndentLevel() + 1);
formatDTD(docType, constraints, start, end, edits);
}

}
}
DTDDeclParameter internalSubset = docType.getInternalSubsetNode();
if (internalSubset == null) {
if (docType.isClosed()) {
int endDocType = docType.getEnd() - 1;
removeLeftSpaces(endDocType, edits);
if (getEnforceQuoteStyle() == EnforceQuoteStyle.preferred) {
int quoteStart = getDocTypeIdStart(docType);
int quoteEnd = getDocTypeIdEnd(docType);

if (quoteStart != -1 && quoteEnd != -1) {
// replace current quote with preferred quote in the case:
// <!DOCTYPE note SYSTEM "note.dtd">
formatterDocument.replaceQuoteWithPreferred(quoteStart,
quoteStart + 1, getQuotationAsString(), edits);
formatterDocument.replaceQuoteWithPreferred(quoteEnd - 1,
quoteEnd, getQuotationAsString(), edits);
}
}
} else {
int endDocType = internalSubset.getEnd() - 1;
String lineDelimiter = formatterDocument.getLineDelimiter();
replaceLeftSpacesWith(endDocType, lineDelimiter, edits);
}
}
DTDDeclParameter internalSubset = docType.getInternalSubsetNode();
if (internalSubset == null) {
if (docType.isClosed()) {
JessicaJHee marked this conversation as resolved.
Show resolved Hide resolved
// Remove space between content and end bracket in case of no internal subset
// Example: <!DOCTYPE note SYSTEM "note.dtd"|>
int endDocType = docType.getEnd() - 1;
JessicaJHee marked this conversation as resolved.
Show resolved Hide resolved
removeLeftSpaces(endDocType, edits);
}
} else {
// Add new line at end of internal subset
// <!DOCTYPE person [...
// <!ENTITY AUTHOR \"John Doe\">|]>
int endDocType = internalSubset.getEnd() - 1;
JessicaJHee marked this conversation as resolved.
Show resolved Hide resolved
String lineDelimiter = formatterDocument.getLineDelimiter();
replaceLeftSpacesWith(endDocType, lineDelimiter, edits);
}
}

private void formatDTD(DOMDocumentType docType, XMLFormattingConstraints parentConstraints, int start, int end,
Expand All @@ -75,20 +94,22 @@ private void formatDTD(DOMDocumentType docType, XMLFormattingConstraints parentC
for (DOMNode child : docType.getChildren()) {
switch (child.getNodeType()) {

case DOMNode.DTD_ELEMENT_DECL_NODE:
case DOMNode.DTD_ATT_LIST_NODE:
case Node.ENTITY_NODE:
case DOMNode.DTD_NOTATION_DECL:
DTDDeclNode nodeDecl = (DTDDeclNode) child;
formatDTDNodeDecl(nodeDecl, parentConstraints, addLineSeparator, edits);
addLineSeparator = true;
break;

default:
// unknown, so just leave alone for now but make sure to update
// available line width
int width = updateLineWidthWithLastLine(child, parentConstraints.getAvailableLineWidth());
parentConstraints.setAvailableLineWidth(width);
case DOMNode.DTD_ELEMENT_DECL_NODE:
case DOMNode.DTD_ATT_LIST_NODE:
case Node.ENTITY_NODE:
case DOMNode.DTD_NOTATION_DECL:
// Format DTD node declaration, for example:
// <!ENTITY AUTHOR "John Doe">
DTDDeclNode nodeDecl = (DTDDeclNode) child;
formatDTDNodeDecl(nodeDecl, parentConstraints, addLineSeparator, edits);
JessicaJHee marked this conversation as resolved.
Show resolved Hide resolved
addLineSeparator = true;
break;

default:
// unknown, so just leave alone for now but make sure to update
// available line width
int width = updateLineWidthWithLastLine(child, parentConstraints.getAvailableLineWidth());
parentConstraints.setAvailableLineWidth(width);
}
}
}
Expand All @@ -114,7 +135,13 @@ private void formatDTDNodeDecl(DTDDeclNode nodeDecl, XMLFormattingConstraints pa
List<DTDAttlistDecl> internalDecls = attlist.getInternalChildren();
if (internalDecls == null) {
for (DTDDeclParameter parameter : attlist.getParameters()) {
// Normalize space at the start of parameter to a single space for ATTLIST, for
// example:
// <!ATTLIST |E |WIDTH |CDATA |"0">
replaceLeftSpacesWithOneSpace(parameter.getStart(), edits);
// replace current quote with preferred quote in the case:
JessicaJHee marked this conversation as resolved.
Show resolved Hide resolved
// <!ATTLIST E WIDTH CDATA "0">
replaceQuoteWithPreferred(nodeDecl, parameter, edits);
}
} else {
boolean multipleInternalAttlistDecls = false;
Expand Down Expand Up @@ -152,7 +179,13 @@ private void formatDTDNodeDecl(DTDDeclNode nodeDecl, XMLFormattingConstraints pa
List<DTDDeclParameter> parameters = nodeDecl.getParameters();
if (!parameters.isEmpty()) {
for (DTDDeclParameter parameter : parameters) {
// Normalize space at the start of parameter to a single space for non-ATTLIST,
// for example:
// <!ENTITY |AUTHOR |"John Doe">
replaceLeftSpacesWithOneSpace(parameter.getStart(), edits);
// replace current quote with preferred quote in the case:
JessicaJHee marked this conversation as resolved.
Show resolved Hide resolved
// <!ENTITY AUTHOR "John Doe">
replaceQuoteWithPreferred(nodeDecl, parameter, edits);
}
}
}
Expand All @@ -175,4 +208,44 @@ private void removeLeftSpaces(int to, List<TextEdit> edits) {
formatterDocument.removeLeftSpaces(to, edits);
}

private String getQuotationAsString() {
return formatterDocument.getSharedSettings().getPreferences().getQuotationAsString();
}

private EnforceQuoteStyle getEnforceQuoteStyle() {
return formatterDocument.getSharedSettings().getFormattingSettings().getEnforceQuoteStyle();
}

private static int getDocTypeIdStart(DOMDocumentType docType) {
if (docType.getPublicIdNode() != null) {
return docType.getPublicIdNode().getStart();
} else if (docType.getSystemIdNode() != null) {
return docType.getSystemIdNode().getStart();
} else
return -1;
}
angelozerr marked this conversation as resolved.
Show resolved Hide resolved

private static int getDocTypeIdEnd(DOMDocumentType docType) {
if (docType.getPublicIdNode() != null) {
JessicaJHee marked this conversation as resolved.
Show resolved Hide resolved
return docType.getPublicIdNode().getEnd();
} else if (docType.getSystemIdNode() != null) {
return docType.getSystemIdNode().getEnd();
} else
return -1;
}

private void replaceQuoteWithPreferred(DTDDeclNode nodeDecl, DTDDeclParameter parameter, List<TextEdit> edits) {
int paramStart = parameter.getStart();
int paramEnd = parameter.getEnd();
if (StringUtils.isQuote(nodeDecl.getOwnerDocument().getText().charAt(paramStart)) &&
StringUtils.isQuote(nodeDecl.getOwnerDocument().getText().charAt(paramEnd - 1))) {
if (getEnforceQuoteStyle() == EnforceQuoteStyle.preferred) {
formatterDocument.replaceQuoteWithPreferred(paramStart,
paramStart + 1, getQuotationAsString(), edits);
formatterDocument.replaceQuoteWithPreferred(paramEnd - 1,
paramEnd, getQuotationAsString(), 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.utils.StringUtils;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextEdit;
Expand Down Expand Up @@ -92,6 +93,12 @@ public static TextEdit createTextEditIfNeeded(int from, int to, String expectedC
matchExpectedContent = to - from == expectedContent.length();
}

// Set parameters to handle case when replacing single quote with double quote and vice versa
if (from == to && !expectedContent.isEmpty() && StringUtils.isQuote(expectedContent.toCharArray()[0])) {
from--;
matchExpectedContent = false;
JessicaJHee marked this conversation as resolved.
Show resolved Hide resolved
}

if (!matchExpectedContent) {
try {
Position endPos = textDocument.positionAt(to);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,10 @@ void replaceLeftSpacesWith(int leftLimit, int to, String replacement, List<TextE
createTextEditIfNeeded(from, to, replacement, edits);
}

void replaceQuoteWithPreferred(int from, int to, String replacement, List<TextEdit> edits){
createTextEditIfNeeded(from, to, replacement, edits);
}

private int getLeftWhitespacesOffset(int leftLimit, int to) {
String text = textDocument.getText();
int from = leftLimit != -1 ? leftLimit : to - 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,13 @@ public void textEdit2() {
assertNotNull(edit);
assertEquals(te(0, 2, 0, 4, " "), edit);
}

@Test
public void textEditQuote() {
TextDocument document = new TextDocument("<a name=\'value \'> </a>", "test.xml");
TextEdit edit = TextEditUtils.createTextEditIfNeeded(8, 9, "\"", document);
assertNotNull(edit);
assertEquals(te(0, 8, 0, 9, "\""), edit);
}

}
Loading